ЕМ>struct Base {
ЕМ> int a;
ЕМ> int b;
ЕМ>};
ЕМ>struct Derived : public Base {
ЕМ> Derived (Base const B) { *this = B; }
ЕМ>};
ЕМ>Derived f (Base const B) {
ЕМ> return B;
ЕМ>}
ЕМ>
ЕМ>Можно ли как-то заставить компилятор приводить типы совсем тупо? Ни reinterpret_cast, ни function-style cast, ни C-style cast не помогают.
Про списки инициализации не слыхал?
struct Derived : public Base {
Derived (Base const B) : Base(B) { }
};
P.S. Я уже не спрашиваю, почему параметр передается по значению, почему конструктор не explicit и зачем оно вообще нужно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Приведение базового класса в конструкторе потомка
Здравствуйте, cserg, Вы писали:
C>Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>>Можно ли как-то заставить компилятор приводить типы совсем тупо? Ни reinterpret_cast, ни function-style cast, ни C-style cast не помогают. C>Вот такое разве не работает? C>
C>Derived(Base const B) { (*(Base*)this) = B; }
C>
по-моему что-нибудь типа
Derived(Base const B) { this->Base::operator=(B); }
будет намного более понятнее чем приведение указателей
Re[8]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
MA>> Как бы я не извращался — результат в rax. На clang тоже в rax. ЕМ>А это уже странно. Они ж должны быть совместимы по ABI.
Что-то вчера был не мой день видимо. Я и проект собирал, ошибся и пол дня он висел без дела, пока я не заметил.
В моём примере: msvc x64 v19.22 для method и virtual_method возвращается адрес на структуру, для static_method возвращается сама структура по значению.
А вот clang 8.0 — в обоих случаях возвращает структуру по значению (через регистр). С -O1 тела методов вообще идентичны.
Насчет должны быть совместимы по ABI — то, афаик, windows abi отличается от amd64 abi.
A scalar return value that can fit into 64 bits is returned through RAX; this includes __m64 types. Non-scalar types including floats, doubles, and vector types such as __m128, __m128i, __m128d are returned in XMM0. The state of unused bits in the value returned in RAX or XMM0 is undefined.
User-defined types can be returned by value from global functions and static member functions. To return a user-defined type by value in RAX, it must have a length of 1, 2, 4, 8, 16, 32, or 64 bits. It must also have no user-defined constructor, destructor, or copy assignment operator; no private or protected non-static data members; no non-static data members of reference type; no base classes; no virtual functions; and no data members that do not also meet these requirements. (This is essentially the definition of a C++03 POD type. Because the definition has changed in the C++11 standard, we don't recommend using std::is_pod for this test.) Otherwise, the caller assumes the responsibility of allocating memory and passing a pointer for the return value as the first argument. Subsequent arguments are then shifted one argument to the right. The same pointer must be returned by the callee in RAX.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>стабильно жалуются на то, что конструктор Derived получается рекурсивным за счет того, что для преобразования Base к Derived используется он же.
А если написать так:
struct Derived : public Base {
Derived(Base const B) : Base(B) { }
};
Re[2]: Приведение базового класса в конструкторе потомка
Здравствуйте, rg45, Вы писали:
R>P.S. Я уже не спрашиваю, почему параметр передается по значению, почему конструктор не explicit и зачем оно вообще нужно.
Передача параметра конструктора по значению в сочетании с std::move — стильно, модно, молодёжно. Но тут Евгений чертовщину какую-то делает. Особенно, если он пытается сделать инициализацию базы.
Re[3]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Чтобы оптимизатор делал передачу структур до 64 разрядов через регистры. Впрочем, для не-POD этого, похоже, не добиться — он всегда генерит временную переменную, даже если по факту никаких вызовов конструкторов/деструкторов там нет (все вычисляется во время компиляции).
R>>почему конструктор не explicit ЕМ>Именно, чтобы можно было преобразовывать неявно.
Я думаю, тебе полезно будет освежить в памяти рекомендации из этой чудесной книженции:
Передача параметров по ссылке (рекомендация 25), использование префиксной формы операторов ++ и -- (рекомендация 28) или подобных идиом, которые при работе должны естественным образом "стекать с кончиков ваших пальцев", преждевременной оптимизацией не являются. Это всего лишь устранение преждевременной пессимизации (рекомендация 9).
Правило №9. Не пессимизируйте преждевременно
Избежание преждевременной оптимизации не влечет за собой снижения эффективности. Под преждевременной пессимизацией мы подразумеваем написание таких неоправданных потенциально неэффективных вещей, как перечисленные ниже. - Передача параметров по значению там, где применима передача параметров по ссылке (рекомендация 25).
Правило №15. Активно используйте const
Пример. Избегайте const в объявлениях функций, принимающих параметры по значению. Два следующих объявления абсолютно эквивалентны:
void Fun( int x );
void Fun( const int x ); // Объявление той же самой функции:
// const здесь игнорируется
Во втором объявлении модификатор const избыточен. Мы рекомендуем объявлять функции без таких высокоуровневых модификаторов const, чтобы тот, кто читает ваши заголовочные файлы, не был дезориентирован...
Правило №40. Избегайте возможностей неявного преобразования типов
Не все изменения прогрессивны: неявные преобразования зачастую приносят больше вреда, чем пользы. Дважды подумайте перед тем, как предоставить возможность неявного преобразования к типу и из типа, который вы определяете, и предпочитайте полагаться на явные преобразования (используйте конструкторы, объявленные как explicit, и именованные функции преобразования типов).
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>объекты небольших структур с недорогим копированием, типа твоего Base, вполне допустимо передавать по значению.
Кстати, еще вспомнил, где это может быть полезно — при работе с множественными значениями типа HRESULT. Стандартно SUCCEEDED/FAILED — это макросы, можно сделать и через глобальные функции, но правильнее было бы оформить HRESULT в виде объекта со встроенными методами анализа, форматирования и т.п.
Но для таких вполне достаточно конструктора, оператора присваивания и методов обработки, а вот для других подобных структур было бы очень удобно иметь возможность и возвращать их по значению.
Re[8]: Приведение базового класса в конструкторе потомка
Здравствуйте, rg45, Вы писали:
R>мне очень трудно представить, чтоб передача параметра по ссылке, вместо передачи по значению, могла бы нанести ощутимый урон производительности.
Да понятно, что реального смысла в такой экономии нет — просто раздражает очевидная странность/тупость компилятора.
R>Так а в чем проблема, возвращай, пожалуйста — все необходимые специальные функции-члены генерируются компилятором автоматически, главное ему не мешать.
Я как посмотрю иногда, сколько кода автоматически генерит компилятор на все эти определения — плакать хочется.
struct Base {
int a;
int b;
};
struct Derived : public Base {
Derived (Base const B) { *this = B; }
};
Derived f (Base const B) {
return B;
}
стабильно жалуются на то, что конструктор Derived получается рекурсивным за счет того, что для преобразования Base к Derived используется он же.
Это особенность компиляторов MS, или оно везде так?
Если бы данных в классе было много, можно было бы копировать через приведение указателей, но структуры до 64 разрядов укладываются в "интегральный тип", хорошо обрабатываются в константных выражениях, и без проблем передаются на регистрах.
Можно ли как-то заставить компилятор приводить типы совсем тупо? Ни reinterpret_cast, ни function-style cast, ни C-style cast не помогают.
Знаю, что можно включить Base в Derived вместо наследования, и наделать операторов преобразования, но хотелось бы именно с наследованием.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Можно ли как-то заставить компилятор приводить типы совсем тупо? Ни reinterpret_cast, ни function-style cast, ни C-style cast не помогают.
Вот такое разве не работает?
Derived(Base const B) { (*(Base*)this) = B; }
Re[2]: Приведение базового класса в конструкторе потомка
Здравствуйте, rg45, Вы писали:
R>Про списки инициализации не слыхал?
Блин. Я был так удивлен невозможностью преобразования типа, что совсем забыл о том, что инициализируется, по сути, базовый класс.
R>почему параметр передается по значению
Чтобы оптимизатор делал передачу структур до 64 разрядов через регистры. Впрочем, для не-POD этого, похоже, не добиться — он всегда генерит временную переменную, даже если по факту никаких вызовов конструкторов/деструкторов там нет (все вычисляется во время компиляции).
R>почему конструктор не explicit
Именно, чтобы можно было преобразовывать неявно.
R>и зачем оно вообще нужно.
В реальном коде 32-разрядная структура из битовых полей. Ее и удобнее, и выгоднее держать на регистре.
Re[2]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
R>>почему параметр передается по значению
ЕМ>Чтобы оптимизатор делал передачу структур до 64 разрядов через регистры. Впрочем, для не-POD этого, похоже, не добиться — он всегда генерит временную переменную, даже если по факту никаких вызовов конструкторов/деструкторов там нет (все вычисляется во время компиляции).
можно написать так:
struct Derived : public Base {
Derived(Base B) : Base(std::move(B)) { }
};
Re[3]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Работает, как и взятие укаателя на B и приведение его к указателю на Derived.
Только нельзя так делать.
ЕМ>Но при этом все заведомо будет в памяти.
Вы ошибаетесь. Операция взятия адреса еще не означает, что адресуемый объект обязательно будет размещен в памяти.
Re[4]: Приведение базового класса в конструкторе потомка
Z>struct Derived : public Base {
Z> Derived(Base B) : Base(std::move(B)) { }
Z>};
Z>
Имхо B тогда протухает и уже недоступно в теле конструктора, могут быть связанные с этим залеты. А так, для условий ТС (B — куча бит без указателей внутри) особого выигрыша по производительности не будет имхо.
Re[3]: Приведение базового класса в конструкторе потомка
Здравствуйте, Hobbes, Вы писали:
H>тут Евгений чертовщину какую-то делает. Особенно, если он пытается сделать инициализацию базы.
Там по фигу, что инициализировать — данные гарантированно одни и те же, база и потомок отличаются только методами.
А чертовщина — это то, что в MS VC++, например, обычный метод класса, возвращающий по значению POD-структуру размером до восьми байт, делает это через регистры. Но стоит сделать этот метод виртуальным, как он возвращает то же самое исключительно через временную переменную на стеке. Ладно бы возвращаемый элемент сам был классом, но это банальная POD-структура, которая от класса, в котором определяется метод, никак не зависит. Я в полных непонятках о причине такой особенности.
Re[4]: Приведение базового класса в конструкторе потомка
Здравствуйте, rg45, Вы писали:
R>Я думаю, тебе полезно будет освежить в памяти рекомендации из этой чудесной книженции:
Я ее как раз недавно перечитывал. Охотно последую этим советам, если кто-нибудь сумеет внятно объяснить, почему 32-разрядный int "безусловно следует" передавать по значению, а 32-разрядную структуру — столь же "безусловно", но по ссылке.
Re[5]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
R>>Я думаю, тебе полезно будет освежить в памяти рекомендации из этой чудесной книженции:
ЕМ>Я ее как раз недавно перечитывал. Охотно последую этим советам, если кто-нибудь сумеет внятно объяснить, почему 32-разрядный int "безусловно следует" передавать по значению, а 32-разрядную структуру — столь же "безусловно", но по ссылке.
Ну насчет "безусловно" это ты загнул, конечно. Ну ОК, объекты небольших структур с недорогим копированием, типа твоего Base, вполне допустимо передавать по значению. Об этом говорится в рекомендации №25. Однако способом передачи по умолчанию все же является передача по константной ссылке. Генеруемые компилятором конструкторы копирования и копирующие операторы присванивания имеют именно такой вид. Все стандартные шаблонные функции, наподобие push_back, например, принимают параметры по константной ссылке и никого не парит возможная потеря производительности при работе с целыми числами. Ну какой смысл в этом промежуточном копировании в конструкторе Derived, если в итоге копирование пойдет через конструктор копирования Base, который все равно будет работать по константной ссылке на эту промежуточную копию Не лучше ли просто пробросить ему ссылку на оригинал?
Ну а что насчет других рекомендаций — касающихся неявных преобразований и модификаторов const при параметрах? Тоже не согласен с чем-то?
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Ну а что насчет других рекомендаций — касающихся неявных преобразований и модификаторов const при параметрах? Тоже не согласен с чем-то?
Разумеется. За использование неявных преобразований имеет смысл пенять только после того, как в языке сделают мало-мальски управляемую систему integral promotions, которая во многих случаях совершенно бессмысленна, и при попытке скрестить ее с шаблонами/перегрузками заставляет городить чрезмерно сложные конструкции. Я на это здесь уже ругался раз
Модификатор const у параметра-значения в объявлении функции действительно не имеет смысла, но объявление ж не существует само по себе — или оно копируется из заголовка определения, или заголовок определения копируется из него. Если в определениях используется const — какой смысл старательно вычищать его из объявлений, или добавлять в определения после копирования?
Re[4]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>А чертовщина — это то, что в MS VC++, например, обычный метод класса, возвращающий по значению POD-структуру размером до восьми байт, делает это через регистры. Но стоит сделать этот метод виртуальным, как он возвращает то же самое исключительно через временную переменную на стеке. Ладно бы возвращаемый элемент сам был классом, но это банальная POD-структура, которая от класса, в котором определяется метод, никак не зависит. Я в полных непонятках о причине такой особенности.
Возможно, причина в том, что в compile-time виртуальный метод не знает, в каком контексте его вызовут и чем там будут какие регистры заняты.
Re[5]: Приведение базового класса в конструкторе потомка
Здравствуйте, Hobbes, Вы писали:
H>Возможно, причина в том, что в compile-time виртуальный метод не знает, в каком контексте его вызовут и чем там будут какие регистры заняты.
А не виртуальный знает что-ли?
Re[6]: Приведение базового класса в конструкторе потомка
Здравствуйте, Hobbes, Вы писали:
H>Возможно, причина в том, что в compile-time виртуальный метод не знает, в каком контексте его вызовут и чем там будут какие регистры заняты.
Э-э-э... А как он тогда с другими параметрами и возвращаемыми значениями обращается?
Тут больше похоже на то, что когда-то просто облажались, перепутав где-то в таблицах признаки объекта класса и виртуальной функции, а когда это обнаружилось, метаться было уже поздно, и пришлось узаконить.
Re[6]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Э-э-э... А как он тогда с другими параметрами и возвращаемыми значениями обращается? ЕМ>Тут больше похоже на то, что когда-то просто облажались, перепутав где-то в таблицах признаки объекта класса и виртуальной функции, а когда это обнаружилось, метаться было уже поздно, и пришлось узаконить.
Я тут на godbolt-е поигрался немного:
#include <utility>
typedef struct {
int a;
int b;
} ValueType;
class MyClass {
public:
ValueType method(int a, int b) {
ValueType v;
v.a = a;
v.b = b + a;
return v;
};
virtual ValueType virtual_method(int a, int b) {
ValueType v;
v.a = a;
v.b = b + a;
return v;
};
static ValueType static_method(int a, int b) {
ValueType v;
v.a = a;
v.b = b + a;
return v;
};
};
int foo(int a, int b) {
MyClass* x = new MyClass();
auto v1 = x->method(a, b);
auto v2 = x->virtual_method(b, a);
auto v3 = MyClass::static_method(a, b);
return v1.a + v2.b + v3.a * v3.b;
}
Как бы я не извращался — результат в rax. На clang тоже в rax.
Пример левой пяткой писан, потому, что он мне internal service error почему-то выдавал постоянно, пока его не "передернешь" с -O2 на -O0 и/или назад.
Примечательно, что при -O2 clang всё это выбрасывает нафиг, оставляя суть, а msvc не особо торопиться что-то выбрасывать.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>А чертовщина — это то, что в MS VC++, например, обычный метод класса, возвращающий по значению POD-структуру размером до восьми байт, делает это через регистры. Но стоит сделать этот метод виртуальным, как он возвращает то же самое исключительно через временную переменную на стеке.
А этот обычный метод класса встраиваемый или нет?
Re[7]: Приведение базового класса в конструкторе потомка
Здравствуйте, Mystic Artifact, Вы писали:
MA> Что-то вчера был не мой день видимо. Я и проект собирал, ошибся и пол дня он висел без дела, пока я не заметил.
И у меня ведь было стойкое убеждение, что я видел, как обычный (нестатический и невиртуальный) метод возвращал структуру по значению.
MA> В моём примере: msvc x64 v19.22 для method и virtual_method возвращается адрес на структуру, для static_method возвращается сама структура по значению.
И правда. Я с этим первые разы экспериментировал еще в 90-х, с тех пор и отложилось. Видать, ни разу не приглядывался с тех пор.
Вот только непонятно, на кой им понадобилось так разделять поведение статических и нестатических членов. Кроме случайной закрепившейся в практике ошибки, на ум ничего не приходит.
Re[10]: Приведение базового класса в конструкторе потомка
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Вот только непонятно, на кой им понадобилось так разделять поведение статических и нестатических членов. Кроме случайной закрепившейся в практике ошибки, на ум ничего не приходит.
Я тоже не понимаю, и скорее всего они сами не понимают, тем более для других компиляторов это не является проблемой.
PS: Я помню, как-то решил обернуть некий то ли указатель, то ли что-то из int в структуру в C# и использовал в PInvoke (просто, что бы добавить какой-то типизированности). И всё работало, ведь на x86 такие структуры возвращаются по значению. Как же я был разочарован когда на линуксе это вообще не поднялось (ведь там они всегда возвращаются по указателю).
Re[7]: Приведение базового класса в конструкторе потомка
R>>объекты небольших структур с недорогим копированием, типа твоего Base, вполне допустимо передавать по значению.
ЕМ>Кстати, еще вспомнил, где это может быть полезно — при работе с множественными значениями типа HRESULT. Стандартно SUCCEEDED/FAILED — это макросы, можно сделать и через глобальные функции, но правильнее было бы оформить HRESULT в виде объекта со встроенными методами анализа, форматирования и т.п.
В то же время, мне очень трудно представить, чтоб передача параметра по ссылке, вместо передачи по значению, могла бы нанести ощутимый урон производительности. В моей практике я встречался с разными причинами замедления, но только не с такими.
ЕМ>Но для таких вполне достаточно конструктора, оператора присваивания и методов обработки, а вот для других подобных структур было бы очень удобно иметь возможность и возвращать их по значению.
Так а в чем проблема, возвращай, пожалуйста — все необходимые специальные функции-члены генерируются компилятором автоматически, главное ему не мешать. Ну либо предоставлять собственные версии, когда это действительно необходимо. А начиная с C++17 возвращать по значению можно вообще что угодно, независимо от доступности конструкторов — благодаря guaranteed copy/move elision. Это обсуждалось здесь не так давно: http://rsdn.org/forum/cpp/7421860.1