LINUX.ORG.RU

Негибкость реализаций Go интерфейсов

 


0

1

Рассмотрим следующий пример кода на Go:

package main

import "fmt"

type animal interface {
	makeSound()
}

type cat struct {}
type dog struct {}

func (c cat) makeSound() {
	fmt.Println("meow")
}

func (d dog) makeSound() {
	fmt.Println("woof")
}

func main() {
	var cat1, dog1 animal = cat{}, dog{}
	cat1.makeSound()
	dog1.makeSound()

	var cat2, dog2 animal = &cat{}, &dog{}
	cat2.makeSound()
	dog2.makeSound()
}

Этот код работает - программа гавкает и мяукает как по значению, так и по ссылке. Но давайте немного изменим код:

package main

import "fmt"

type animal interface {
	makeSound()
}

type cat struct {}
type dog struct {}

func (c *cat) makeSound() {
	fmt.Println("meow")
}

func (d *dog) makeSound() {
	fmt.Println("woof")
}

func main() {
	var cat1, dog1 animal = cat{}, dog{}
	cat1.makeSound()
	dog1.makeSound()

	var cat2, dog2 animal = &cat{}, &dog{}
	cat2.makeSound()
	dog2.makeSound()
}

Теперь вызовы cat1.makeSound() и dog1.makeSound() не компилируются, выдавая ошибки вроде следующей:

.\test.go:21:6: cannot use cat{} (type cat) as type animal in assignment:
	cat does not implement animal (makeSound method has pointer receiver)

При этом вызовы cat2.makeSound() и dog2.makeSound() продолжают работать.

Почему такое неконсистентное поведение и почему передача того, что в других языках называется this или self в Go не унифицирована? В отличии от других языков программист на Go должен думать не только о сигнатуре метода, но и о способе передачи аналога this или self.

Кстати, объявить одновременно два варианта метода нельзя. Например если написать вот так:

func (c *cat) makeSound() {
	fmt.Println("meow")
}

func (c cat) makeSound() {
	fmt.Println("meow")
}

Будет ошибка:

.\test.go:16:6: method redeclared: cat.makeSound
	method(*cat) func()
	method(cat) func()

Ответ на: комментарий от alysnix

то есть копировать там просто нечего. и пишете функции передачи типа нулевой длины по ссылке и значению.

Не имеет никакого значения, важно то, какие конструкторы вызываются.

затем. вы пишете функцию куда передается по значению обьект нулевой длины… и о чудо! она оказывается совсем короткой. ну правильно. вы в функцию передали только адрес источника, длина ноль, ничто не копируется. код короткий. или вы думаете компилятор вам сгенерит код для копирования нуля байт?

Какой код, какая длина, какое копирование? Пример иллюстрирует эффективность передачи moveable типов по значению. Я могу написать там рабочий класс строки, сделать к нему SBO, но зачем? Смысл показать в том, как будет вызываться конструкторы. Очевидно, что в копирующем конструкторе будет копироваться содержимое строки, а в перемещающем – нет, только три указателя.

в таком случае компилятор вряд ли станет генерить код взятия копии, поскольку с этой нет вообще никакой работы и соптимизирует его. сделайте хоть что-то с копией то!

Он не имеет права выкидывать конструкторы, так как в них сайд-эффекты.

и уберите там писанину в сout, не засоряйте год всякой чепухой.

Не засоряйте свою речь всякой чепухой. «Писанина в cout» является доказательством того, о чем я говорил, и отлично демонстрирует то, как в С++ отрабатывают конструкторы при передаче аргументов в функцию.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Не имеет никакого значения, важно то, какие конструкторы вызываются.

Ok.
Далее не упрек вам.

Интересно, почему так часто обсуждают работу с классами?
Уж и не помню, когда их последний раз использовал.
Не потому, что брезгую ними, а потому что для, разрабатываемого API /а оно не такое уж и тривиальное/ они просто не нужны.
Просто УЖАСАЮСЬ, если бы API для возможности создания/работы/… объектов на основании метаданных использовало бы классы.
API весьма эффективно, потому как оно не использует АБСТРАКЦИИ от ВЫСОКОУМИЯ … При их использовании в контексте выше сказанного сам себя бы

ОТХЛЕСТАЛ
anonymous
()
Ответ на: комментарий от Siborgium

Пример иллюстрирует эффективность передачи moveable типов по значению.

а вы не видите там, что компилятор в данном режиме делает копию извне, и это нужно для того, чтобы иногда использовать movable.

то есть код вызова ВСЕГДА вздувается минимум вдвое, ради того, чтоб ИНОГДА, не было вызова лишнего копирования.

вот вызов по конст.ссылке и по копии, функции где параметр - просто локальная переменная

конст ссылка

        leaq    -2800(%rbp), %rax
        leaq    -3200(%rbp), %rdx
        movq    %rdx, %rsi
        movq    %rax, %rdi
        call    pass_by_ref(string_like const&)

с копией

        leaq    -3200(%rbp), %rdx
        leaq    -2000(%rbp), %rax
        movq    %rdx, %rsi
        movq    %rax, %rdi
        call    string_like::string_like(string_like const&) [complete object constructor]
        leaq    -2400(%rbp), %rax
        leaq    -2000(%rbp), %rdx
        movq    %rdx, %rsi
        movq    %rax, %rdi
        call    pass_by_value(string_like)

из 5 команд вызова стало 10, это называется эффективностью? а размер кода уже не является одним из факторов эффективности?

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

верней вот код - по ссылке и по значению, для обычной переменной

        leaq    -3(%rbp), %rax
        movq    %rax, %rdi
        call    pass_by_ref(string_like const&)
        leaq    -3(%rbp), %rdx
        leaq    -2(%rbp), %rax
        movq    %rdx, %rsi
        movq    %rax, %rdi
        call    string_like::string_like(string_like const&) [complete object constructor]
        leaq    -2(%rbp), %rax
        movq    %rax, %rdi
        call    pass_by_value(string_like)

предыдущий код..это я там что-то экспериментировал, а это ваш пример. вместо 3 команд 8… то есть второе как бы эффективней первого..:(

alysnix ★★★
()
Ответ на: комментарий от alysnix

Все верно, но боюсь, что ответа вы не дождетесь. Оппонент, осознав свою неправоту, позорно слился.

anonymous
()
Ответ на: комментарий от anonymous

Оппонент, осознав свою неправоту, позорно слился.

ну почему. оппонент привел довольно удачный пример некоей обещанной эффективности(в частных случаях) в передаче по значению…только не обратил внимания на накладные расходы (по коду) которые немедля отягощают любой вызов подобной функции.

понятно, что копирование снаружи это некий п…ц. и допущен был такой п..ц только для того, чтобы сообщить бандам упоротых плюсовиков, что над мрачной долиной прежней «неэффективности» взошло солнце мувабл копирования.

я вот только укрепился во мнении, что ничто кроме скаляра передавать по значению в плюсах уже нельзя. если до мувабла, копирование наверняка делалось изнутри функции и все было чистенько, то теперь все стало грязьненко и это не стоит такого роста кода.

а почему копировать снаружи это пепец - очевидно.

  1. рост кода, даже для одного параметра, и чем их больше, тем рост страшней.

  2. надо еще посмотреть какие там будут копирования, если нет конструктора копии…может там будут какие-то лютые инлайны и код вырастет уже даже не вдвое, а втрое.

  3. поскольку функция получает копию снаружи, а не делает ее сама, она уже это место оптимизировать не может. например, если параметр не используется - копию можно было и не делать. а если используется только на чтение - то воспринимать это все как конст ссылку и копию опять не делать… а по поводу «сайдэфекта» конструктора копии написать в стандарте аршинными буквами, что компилятор не обязан делать копию, если видит, что копия там не нужна. просто, понятно и логично. хотите программировать на сайдэффектах конструкторов копий - пишите на асме.

и главный убойный аргумент - передача по копии это явная зависимость интерфейса от реализации. что есть смертный грех и отлучение от церкви.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

то есть код вызова ВСЕГДА вздувается минимум вдвое, ради того, чтоб ИНОГДА, не было вызова лишнего копирования.

Хоть втрое – это гораздо дешевле, чем копировать все содержимое строки.

из 5 команд вызова стало 10, это называется эффективностью?

Зачем вы врете? Для чего вы это делаете? Почему в «5 команд» вы не включили копирование строки внутри?

а размер кода уже не является одним из факторов эффективности?

Одним из факторов является, но далеко не ключевым.

Siborgium ★★★★★
()
Ответ на: комментарий от alysnix

только не обратил внимания на накладные расходы (по коду) которые немедля отягощают любой вызов подобной функции.

…которые не превышают «накладных расходов» внутри этой функции в альтернативном подходе, а в ряде случаев оказываются дешевле.

понятно, что копирование снаружи это некий п…ц

Нет, не понятно.

копирование наверняка делалось изнутри функции и все было чистенько,

Еще раз, почему вы упорно игнорируете копирование внутри функции? Под одеялом Аллах не видит?

а почему копировать снаружи это пепец - очевидно.

Нет, не очевидно.

  1. рост кода, даже для одного параметра, и чем их больше, тем рост страшней.

Еще раз, нет смысла вычислять производительность ассемблера по его размеру. -Osize продуцирует куда меньшие бинарники, чем -Ofast – и куда более медленные.

написать в стандарте аршинными буквами, что компилятор не обязан делать копию, если видит, что копия там не нужна.

Стандарт ясно описывает условия для copy elision.

и главный убойный аргумент - передача по копии это явная зависимость интерфейса от реализации. что есть смертный грех и отлучение от церкви.

О как запели! Только это не «передача по копии», а передача по значению.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Хоть втрое – это гораздо дешевле, чем копировать все содержимое строки.

а причем тут строка? в вашем примере не строка, а «типастрока» вообще без содержимого. будь там хоть один int, все будет тем же самым,и инт будет копироваться через вызов конструктора копии.

это вы говорите о строках. а я говорю о структурных типах.

Зачем вы врете? Для чего вы это делаете? Почему в «5 команд» вы не включили копирование строки внутри?

никто не врет. внутренний код пролога функции вынесен наружу и пишется при всех ее вызовах. по числу исполняемых инструкций до запуска полезного кода функции это примерно одно и тоже, а по обьему - кода - нет. а малый обьем кода это больше кеш хитов, что влияет на скорость.

Одним из факторов является, но далеко не ключевым.

просто не надо заниматься куэтой и стараться подобрать интерфейс функции под действия которые она исполняет. в 95 процентах случаев копия будет просто не нужна. и заниматься подбором «правильного интерфейса функции» для чего-то там особеспециального внутри нее - глупость.

alysnix ★★★
()
Ответ на: комментарий от Siborgium

…которые не превышают «накладных расходов» внутри этой функции в альтернативном подходе, а в ряде случаев оказываются дешевле.

приведите этот «ряд дешевых случаев».

О как запели! Только это не «передача по копии», а передача по значению.

не придирайтесь к словам, это то что я и сказал.

зы: и вообще, покажите хоть одну распространенную либу, где б хотя бы тип-строка передавалась по значению. интересно посмотреть на коммент к этой декларации функции

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

будь там хоть один int, все будет тем же самым,и инт будет копироваться через вызов конструктора копии.

Нет.

это вы говорите о строках. а я говорю о структурных типах.

Да? Вообще-то разговор идет о строках, а точнее std::string.

никто не врет. внутренний код пролога функции вынесен наружу и пишется при всех ее вызовах.

Наплевать, куда он пишется. Вы действительно невменяемы, какая вообще разница, куда пишется пролог, если затраты на исполнение пролога по сравнению со затратами на копирование содержимого строки ничтожна?

по числу исполняемых инструкций до запуска полезного кода функции это примерно одно и тоже, а по обьему - кода - нет.

Инструкция инструкции рознь, абстрактное число инструкций считать бессмысленно.

а малый обьем кода это больше кеш хитов, что влияет на скорость.

Какие «кеш хиты», болезный? Что ты несешь? Ты блеешь про вред инлайнинга и сам же сообщаешь про «кеш хиты». Позорище. Слился – начинай блеять про кэш. Рецепт успеха.

просто не надо заниматься куэтой и стараться подобрать интерфейс функции под действия которые она исполняет

Интерфейс функции описывает те действия, которые она исполняет. Иначе никакого смысла в этом интерфейсе нет.

в 95 процентах случаев копия будет просто не нужна. и заниматься подбором «правильного интерфейса функции» для чего-то там особеспециального внутри нее - глупость.

Что ты несешь? Откуда ты взял «нужна», «не нужна» и так далее? Вводная следующая была: копия нужна.

Siborgium ★★★★★
()
Ответ на: комментарий от alysnix

приведите этот «ряд дешевых случаев».

Я привел ссылку на godbolt выше.

не придирайтесь к словам, это то что я и сказал.

Нет. Передача по значению подразумевает mandatory copy elision из prvalue, т.е.

pass_by_value(returns_string());

вызовет только один конструктор строки. Никаких копий там не будет.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Да? Вообще-то разговор идет о строках, а точнее std::string.

это у вас он идет о строках, у меня о структурных типах. начинайте ориентироваться. в вашем коде никаких строк тоде нет, но выводы вы по коду там делаете.

Инструкция инструкции рознь, абстрактное число инструкций считать бессмысленно.

отливаю в граните.

Ты блеешь про вред инлайнинга и сам же сообщаешь про «кеш хиты».

это какой то п..ц.

Интерфейс функции описывает те действия, которые она исполняет. Иначе никакого смысла в этом интерфейсе нет.

параллельно отливаю в граните.

Что ты несешь? Откуда ты взял «нужна», «не нужна» и так далее? Вводная следующая была: копия нужна.

такой вводной не было. была вводная что в 95 проц. случаев она не нужна. вот это вводная, так вводная!

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от Siborgium

вызовет только один конструктор строки. Никаких копий там не будет.

ссылочка на либу, где ваши мыслищи нашли документальное воплощение в декларациях методов, ну или функций, будет?

пошел спать. ссылочка будет уже завтра. это хорошо.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

это у вас он идет о строках, у меня о структурных типах. начинайте ориентироваться. в вашем коде никаких строк тоде нет, но выводы вы по коду там делаете.

Шизоид, ты опять начинаешь?

Негибкость реализаций Go интерфейсов (комментарий)

korvin: если внутри функции надо строку скопировать и куда-то сохранить на память, а не просто по ней пройтись, лучше наоборот принимать по значению.

alysnix: песдешь. он плохой плюсовик. он даже не понимает, что «строка» в плюсах, это структурка, с указателем на буфер в куче. и все данные (то есть сама


отливаю в граните.

это какой то п..ц.

параллельно отливаю в граните.

Ясно, оправдания закончились, персонаж перешел от отрицания к торгу.

такой вводной не было. была вводная что в 95 проц. случаев она не нужна. вот это вводная, так вводная!

Нет, эту вводную только что придумал ты. Выше цитаты на начало, в которых ясно написано, что копия нужна всегда.

ссылочка на либу, где ваши мыслищи нашли документальное воплощение в декларациях методов, ну или функций, будет?

https://habr.com/ru/post/479358/ см. таблицу

https://stackoverflow.com/questions/4321305/best-form-for-constructors-pass-by-value-or-reference

https://stackoverflow.com/questions/51705967/advantages-of-pass-by-value-and-stdmove-over-pass-by-reference

https://web.archive.org/web/20140205194657/http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 2)
Ответ на: комментарий от Siborgium

ссылку на либу! и не пустопорожние посты на хабре и переполнении стека. а то вы вместо ссылки на известную либу, скоро будете давать ссылки на самого себя, отлитые в граните.

итак. ссылку на либу. даю определение «известной либы» - набор файлов и компонент, широко применямых в разработке на с++, хидеры или доки которых можно посмотреть.

давать вместо этого пост на переполнении стека или хабре!!! - жульничество.

меня вообще не интересуют там слова всяких чулавеков, а заголовки функций.

это меня разбудили..

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от anonymous

Хороший тред

Для себя сделал вывод такой

Не нужно на пустом месте создавать себе проблемы и решать их ...
anonymous
()
Ответ на: комментарий от alysnix

Бездарность, бегом в школу. С какого хрена библиотеки будут принимать по значению, если они могут перегрузить и const T&, и T&&? Понимание какого из слов в предложении «‘передача по значению’» эффективнее передачи по const ref&+копирования" вызывает у тебя затруднение?

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от alysnix

«строка» в плюсах, это структурка, с указателем на буфер в куче. и все данные (то есть сама строка символов) находятся на куче. и «копирование строки» это не копирование буфера на куче, а просто копирование структурки. новая структурка будет указывать на тот же буфер.

А ты совсем плох.

Stil ★★★★★
()
Ответ на: комментарий от Stil

Пропустил этот перл. Наш грамотный плюсовик сражен старческим маразмом и застрял в С++98, вместе с CoW строками.

Siborgium ★★★★★
()
Ответ на: комментарий от Stil

А ты совсем плох.

Далее не о @alysnix речь.

Железо ныне весьма производительно и «любой код» будет БЫСТР …

Это радует и печалит ...

Далеко не все программисты понимают где уместно const, *, &, &&, *&, **, … и почему …

anonymous
()
Ответ на: комментарий от Siborgium

Понимание какого из слов в предложении «‘передача по значению’» эффективнее передачи по const ref&+копирования" вызывает у тебя затруднение?

у меня затруднение вызывает поиск любой разумной либы, работающей ну хотя б с любимыми вами строками, в которой были б определены функции вида.. ff(std::string s)

а не

ff(const std::string &s) - вот этого добра навалом, верней оно везде так.

сразу говорю что попросил вас найти первый вариант деклараций. и хватит вешать лапшу про «рефкопирование». вопрос совершенно конкретный и простой.

alysnix ★★★
()
Ответ на: комментарий от Siborgium

С какого хрена библиотеки будут принимать по значению, если они могут перегрузить и const T&, и T&&?

какая-то либа перегружает операторы const T&??? то есть сначала пишет

void ff(const T& s)… а потом перегружает этот оператор??? а какая мощная идея за этим сокрыта? это наверное чтоб юзеру нескучно было.

опять отлив в граните… ну хоть покажите такую либу что-ли. это типа везде написать оператор плюс.. а потом для веселья перегрузить его на умножить. а умножить перегрузить на делить, а конструктор копии на форк процесса, крута как! а конструктор без параметров, на добычу битокина, во! а че! можно же!!! - тема диссертации - «психические отклонения у подростков и синтаксис с++».

короче ищите либу, что тем или иным образом берет строки по значению, как вы тут советуете.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 3)

Ты очередная инкарнация anonimous? Просто это в его стиле засунуть себе палку в колесо и начать орать на палку, вместо того что бы научиться нормально ездить. Не нравится го - не пиши на го. Или сделай форк и измени поведение.

Aswed ★★★★★
()
Ответ на: комментарий от anonymous

что нового в разработке IDE?

там все зашибись. приподнял на новую высоту, ширится и растет широкой поступью

alysnix ★★★
()
Ответ на: комментарий от alysnix

void ff(const T& s)… а потом перегружает этот оператор??? а какая мощная идея за этим сокрыта? это наверное чтоб юзеру нескучно было.

Бездарный кретин.

f(const std::string&);
f(std::string &&);

Примеры: folly/fbstring, glibmm/ustring, Qt/QString, сама STL.

короче ищите либу, что тем или иным образом берет строки по значению, как вы тут советуете.

Идиот, процитируй мне, где я кому-то советую так делать.

Я утверждаю, что это эффективнее передачи по const &+копирования. При условии, что строка будет передаваться еще куда-то, можно сделать еще эффективнее, ценой 2^n перегрузок для каждого параметра – или используя perfect forwarding, но это принуждает выносить код в хэдеры.

опять отлив в граните… ну хоть покажите такую либу что-ли. это типа везде написать оператор плюс.. а потом для веселья перегрузить его на умножить. а умножить перегрузить на делить, а конструктор копии на форк процесса, крута как! а конструктор без параметров, на добычу битокина, во! а че! можно же!!! - тема диссертации - «психические отклонения у подростков и синтаксис с++».

В игнор.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 2)
Ответ на: комментарий от Siborgium

Я утверждаю, что это эффективнее передачи по const &+копирования.

третий круг про эффективность? эффективно, только если обьект там подставили временный. потому что копия уже есть. ценой создания копии извне полюбому –> раздувания кода вызова такой функции.

а зачем вам вообще эффективность столь частного случая-то?

процитируй мне, где я кому-то советую так делать.

эти две цитаты стоящие рядом, обнаруживают за ними шизоидного типа, который с одной стороны УТВЕРЖДАЕТ что это эффективнее!, с другой - «а где я советовал так делать?».

как-то надо выбрать между вариантами «эффективней и советую», и «эффективней - но не советую».

я вот советовал передавать по ссылке(как во всех приличных либах), и не парить себе мозг частностями. вы же выдвинули лозунг - а по значению лучше!!!… и все закончилось тем, что вопрошаете - «а где я вообще это советовал?»

о какой перегрузке операторов вы там трендите…если просят просто показать в любой приличной либе, заголовок функции вида somefunc(std::string fs), то есть передачи по значению, которую вы считаете эффективней?

alysnix ★★★
()
Ответ на: комментарий от Siborgium

Я люблю раст и С …

Владимир

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.