Не за меня. Так я ж не все. Может я конечно упустил, где в C++ Guildelines этот момент ловится, или clang-tidy может словит? В любом случае — это намеренное проталкивание ногострелов, когда без них можно было обойтись без какой бы то ни было потери производительности кода.
Хоть у меня там всё НА ССЫЛКАХ, это таки скомпилируется. Даже ворнинга не приходит. И это страшно. Конечно, можно верить, что дураки не прикоснутся к вашему коду и т.д. Но жизнь расставляет всё по местам.
class Owner {
protected:
BigObject o;
Resource r;
Mutex m {r};
[[order:r,o]] // order r->m will be derived automatically, s.t. it becomes [[order:r,m,o]] by the compiler
};
И мне это даёт всё сразу. Компилер выведет порядок r->m, потому что я явно о нём пишу. Сейчас я могу переставить две эти строки и получится ерунда. Для o я ПОЧЕМУ-ТО хочу, чтобы o шло после r. Пожалуйста — задаю это. Всё явно, компактно, и нет прочих требований, связанных с тем, что этот порядок будет влиять на то, как вы выписываете код, использующий Owner. Я не понимаю, почему развязку и наполнение ациклических графов зависимостей нельзя возложить на компилятор, и я должен этим заниматься.
Естественно, когда задизайнили это нынешнее правило с порядком, не было аннотаций, как минимум. Но чем они думали (или может быть даже ОН? не упомню, писал ли он об этом конкретно аспекте в Design and Evolution of C++), когда видели, что даже когда r явно зависит от m, компилятору на это по боку, и язык ничего не требует. Бред же. Вы защищаете плохой дизайн, я считаю.
Повторюсь — я отлично понимаю, чего добивались таким дизайном авторы языка. Решили не плодить сущности без необходимости, встроили порядок инициализации в порядок объявлений. Окей. Я считаю, это ошибка — вписывать низкоуровневый аспект физического дизайна в логическую структуру кода. Нужна была другая сущность. Это всё можно сделать zero-cost, конечно же.
Более того, я считаю, что именно этим правилом они и порочат zero-cost abstraction принцип. Цена этой абстракции — что с новыми фичами код теперь надо причёсывать на каждую перегруппировку переменных. Цена — производительность итоговой программы ценой этой вот возни, стоящей времени программистов, когда как ту же производительность можно было бы сохранить введением явного означения порядка как отдельной сущности. Да и даже улучшить — некоторые, например, просто не пожелают лишний раз перегруппировывать порядок определений, дабы не рефакторить массу кода.
Хорошо, фиг с ним, с этим поинтером. Согласен, поинтер перебор. Облегчим структуры. Что хотел показать тем примером: godbolt.org/z/OVirOQ (моё) / godbolt.org/z/LFds9y (ваше). Один-в-один. Собственно, я хотел явности в порядке, оно теперь у меня есть. Совсем по-хорошему нужен ещё свой std::reference_wrapper, который бы можно было инициализировать nullptr с дебаг-ассертами на обращение к нему пустому, чтобы не ничего не делать с сырым ptr. Да, здесь я, безусловно, борюсь с языком, лишь бы не полагаться на «фундаментальное правило», ожидая такой защитой, что кто-то о нём точно забудет. Что ж поделать, раз так задизайнили язык.
Это физика. Меняю порядок полей для оптимизации — почему это должно сказываться на физике назначенной инициализации? Почему эта локальная оптимизация (делаю поля соответствующими схеме доступа к ним) должна меня заставлять перетасовывать прочие места кода? И вообще, по-хорошему, переупорядочить поля согласно паттерну доступа, кто там первый загрузится в кэши с кодом и т.д. — это задачка для PGO.
Ещё раз: не путайте python с C++! В C++ очень много чего происходит неявно, стараниями компилятора. Это — почти что строго противоположный подход.
Извините, я считаю, порядок ради порядка не имеет смысла. Компилятор за вас может упорядочить аргументы из неупорядоченного списка-множества. Зачем вручную делать работу компилятора? Это же противоречит идее С++ — вы сами выше говорите, «компилятор много чего делает за нас», так? Так.
Была бы моя воля — не юзал бы Питон вообще кроме как в билд-системе, и делал бы нейронные сети на C++. Но из-за ограничений языка всё это получается настолько многословным, что деревья теряются в лесу.
А зачем его в уме-то запоминать? Компилятор ошибку выбросит, вы исправите… делов-то. IDE, опять-таки, подсказку может показать…
Ну я что могу сказать. Если бы я продавал часы — это мне всё на руку было бы. Как можно хорошо плюсануть к ивойсу это всё. Но так как я сам себе заказчик, а замены С++ по эффективности нет, вся эта мышиная возня мне сооовсем не на руку.
Я хочу элементарно писать код в pythonic-стиле, то бишь — прозрачно, компактно. Мы получаем плюшки в эту сторону, не скрою. Сколько мы их ждали, правда — другой вопрос. Поэтому, безусловно, в 2019 я могу себе позволить писать С++-код в куда более Pythonic-стиле, чем в 2011.
Да, в C++ есть вот такие вот, несколько странные и неожиданные «правила игры» — но это плата за эффективность.
Я, конечно, буду здесь голословным, ибо в комитет, к сожалению, не вхож. Если бы ребята алгебраически подошли к проблеме построения системы типов, то пруверы им бы указали на все эти недочёты и промахи, которые они продолжают вносить и исправлять, вносить и исправлять. Я таки не понимаю, как бинарная несовместимость классов с разным порядком одинаковых членов (по сути это всё разные классы бинарно) связана с эффективностью.
От умных указателей вы тоже, стало быть, отказываетесь? Там же неявно где и как они память освободят!
А почему надо отказываться? .reset( в нужном порядке ему, счётчик ссылок далее позаботится об удалении. В том примере выше я тоже ничего не удалял delete-ом, хотя вызов reset этому эквивалентен, но порядок удаления указал совершенно жёстко. Или вы о порядке для класса, содержащегося в shared_ptr-объекте?
Дык я только и хочу их перетасовать, что у C++ такие правила выравнивания :) У нормальных людей класс — это логическая единица, неупорядоченный набор полей и методов. В С++ класс имеет мало общего с этим определением, т.к. он сразу тебя окунает в свою физику.
Я как раз об этих правилах. Виртуальная функция — это типа указатель, со всеми вытекающими. Снова возвращаемся к фундаментальному правилу. Это мне и вам понятно. Понятно ли начинающим? Отнюдь. Все нормальные люди и начинающие считают класс НЕУПОРЯДОЧЕННЫМ множеством. ООП. В C++ же оно оказывается скорее упорядоченным, нежели нет. Скажем, частично и упорядоченным. Об этом кричат? Нигде не встречал. :) Хотя, о чём кричат на CppCon и иже с ним — о том, что «нам учить других этому языку, и с этим гигантская проблема».
А потом начинается ещё… Поля в структурке перетасовать, чтобы утрясти размер, а тут порядок понимаешь ли, и т.д. и т.п. Знай сиди и рефактори до бесконечности свои назначенные инициализации :)
Я вижу, и не я один. Случайно поменять порядок объявлений очень легко, особенно если работаешь не только с С++. Варнингов типа «warning: order of member declarations changed since last time» у нас нет и не будет. Потому — defensive programming, явно задаём порядок кодом, если он нужен, не полагаемся на сомнительную фичу. Она безусловно детерминирована, но настолько неявна, что лучше её полностью игнорировать.
Я совершенно понимаю, что они хотели ей сказать — что, мол, типа, раз в куске кода переменные у нас создаются на стеке в порядке их появления в коде, то пусть так будет и в scope класса, круто же, единообразно? Но, нет, не круто. Люди не смотрят на класс как на линейный код, потому что класс — не линейный код. Это НЕУПОРЯДОЧЕННЫЙ набор полей и методов.
Ну как. Я живу в C++ с 2001 года, а вот к .NET и Python лишь захаживаю. Привыкнуть за 18 лет к странному дизайну с порядком инициализации, заданном именно порядком декларациями полей, как-то не довелось и не желалось. Вернее, ни разу не доводилось на него полагаться — в случаях зависимостей и нужности порядка задавал его явно. Явное лучше неявного. Теперь же эта «фича» ещё и продолжает ограничивать развитие языка.
А вот наблюдать за адекватным развитием некоторых языков доводится с завистью. Хочется все 18 лет писать менее многословный прикладной код на С++ без птичьего щебета, запоминания порядка переменных в уме и мрака вроде www.boost.org/doc/libs/1_62_0/libs/parameter/doc/html/index.html. Экстраполировать отсутствие естественных фич в языке многоэтажным метапрограммированием — это как-то не очень.
Спасибо. Это, правда, не «нутро» RAII, а всё же частный случай. Но будет ли в этом частном случае хорошим тоном полагаться на порядок, выбранный компилятором? Очень уж неявно — почва для ошибок. Human first, вы же понимаете. Разве не стоит сделать что-то типа:
Конечно, если я СЛУЧАЙНО поменяю порядок строк с r и m местами, будет беда. Захотеться случайно менять этот порядок, если я внесу его в конструктор, уже не должно. Попробуйте.
Мне кажется, очень уж самонадеянно и высоколобо было отдавать именно порядок инициализации и деинициализации на роль «RAII для класса как scope».
Я понимаю, что RAII, copy elision и пр. полагаются на фундаментальное свойство. Я не могу ответить, как. Я хотел бы иметь этот ответ, чтобы объяснить изучающим С++: «это так, ПОТОМУ ЧТО вот», а не просто «потому что». Пока не могу. Может быть, поможете :)
Конкретно, вот например RAII. Почему ИМЕННО ему важен детерминированный порядок инициализации и разрушения?
Хочется показать 2-3 примерами, что фундаментальное свойство — так его и назовём — не вещь в себе, а требование корневых механизмов С++.
Да, согласен. Без генерации компилятором отнаследованных классов с виртуальными деструкторами здесь не обойтись, никак не запомнить порядок инициализации полей для его раскрутки при разрушении объекта. Пойдём тогда ещё дальше — насколько действительно важно это «фундаментальное свойство» в прикладном коде на С++17? По мне так это скорее исключение, когда у вас эти поля ходят как-то друг к другу изнутри через ссылки или указатели друг на друга, и вам нужно отконтролить их жизненный цикл в совокупности — развязать направленный граф, так сказать. Часто ли?
Мы не должны мириться с тем, что комитет загнал в угол сам себя.
По сути: кому помогал тот факт, что поля класса инициализируются в порядке их объявления в классе? Это массовый источник заблуждений. Все ожидают, что они инициализированы так, как ты записал в списке инициализации конструктора, и мне до сих пор приходится себя одергивать, когда я его пишу. То, что порядок инициализации не перегружен порядком, заданным мной к конструкторе, меня удивляет. Можно себя очень глубоко загнать — godbolt.org/z/NpFiI1:
#include <iostream>
struct A { int i; A(int i) : i(i) { std::cout << i << "-a "; } };
struct B { int i; B(int i) : i(i) { std::cout << i << "-b "; } };
struct S {
B b;
A a;
S() : a(2), b(a.i) {}
};
int main() {
S s;
}
Дальше, список инициализации в конструкторе не требует сохранять порядок как в классе, хотя оператор запятая у нас работает строго слева направо. Почему в списке инициализации можно, а в тэгированной инициализации нельзя? Может быть, комитету стоило поправить это место, и разрешить перегрузку порядка инцициализации с по-классовой к по-списочной?
Это комитет расстреливает нам ноги.
Хоть у меня там всё НА ССЫЛКАХ, это таки скомпилируется. Даже ворнинга не приходит. И это страшно. Конечно, можно верить, что дураки не прикоснутся к вашему коду и т.д. Но жизнь расставляет всё по местам.
И мне это даёт всё сразу. Компилер выведет порядок r->m, потому что я явно о нём пишу. Сейчас я могу переставить две эти строки и получится ерунда. Для o я ПОЧЕМУ-ТО хочу, чтобы o шло после r. Пожалуйста — задаю это. Всё явно, компактно, и нет прочих требований, связанных с тем, что этот порядок будет влиять на то, как вы выписываете код, использующий Owner. Я не понимаю, почему развязку и наполнение ациклических графов зависимостей нельзя возложить на компилятор, и я должен этим заниматься.
Естественно, когда задизайнили это нынешнее правило с порядком, не было аннотаций, как минимум. Но чем они думали (или может быть даже ОН? не упомню, писал ли он об этом конкретно аспекте в Design and Evolution of C++), когда видели, что даже когда r явно зависит от m, компилятору на это по боку, и язык ничего не требует. Бред же. Вы защищаете плохой дизайн, я считаю.
Более того, я считаю, что именно этим правилом они и порочат zero-cost abstraction принцип. Цена этой абстракции — что с новыми фичами код теперь надо причёсывать на каждую перегруппировку переменных. Цена — производительность итоговой программы ценой этой вот возни, стоящей времени программистов, когда как ту же производительность можно было бы сохранить введением явного означения порядка как отдельной сущности. Да и даже улучшить — некоторые, например, просто не пожелают лишний раз перегруппировывать порядок определений, дабы не рефакторить массу кода.
Извините, я считаю, порядок ради порядка не имеет смысла. Компилятор за вас может упорядочить аргументы из неупорядоченного списка-множества. Зачем вручную делать работу компилятора? Это же противоречит идее С++ — вы сами выше говорите, «компилятор много чего делает за нас», так? Так.
Была бы моя воля — не юзал бы Питон вообще кроме как в билд-системе, и делал бы нейронные сети на C++. Но из-за ограничений языка всё это получается настолько многословным, что деревья теряются в лесу.
Ну я что могу сказать. Если бы я продавал часы — это мне всё на руку было бы. Как можно хорошо плюсануть к ивойсу это всё. Но так как я сам себе заказчик, а замены С++ по эффективности нет, вся эта мышиная возня мне сооовсем не на руку.
Я хочу элементарно писать код в pythonic-стиле, то бишь — прозрачно, компактно. Мы получаем плюшки в эту сторону, не скрою. Сколько мы их ждали, правда — другой вопрос. Поэтому, безусловно, в 2019 я могу себе позволить писать С++-код в куда более Pythonic-стиле, чем в 2011.
Я совершенно понимаю, что они хотели ей сказать — что, мол, типа, раз в куске кода переменные у нас создаются на стеке в порядке их появления в коде, то пусть так будет и в scope класса, круто же, единообразно? Но, нет, не круто. Люди не смотрят на класс как на линейный код, потому что класс — не линейный код. Это НЕУПОРЯДОЧЕННЫЙ набор полей и методов.
А вот наблюдать за адекватным развитием некоторых языков доводится с завистью. Хочется все 18 лет писать менее многословный прикладной код на С++ без птичьего щебета, запоминания порядка переменных в уме и мрака вроде www.boost.org/doc/libs/1_62_0/libs/parameter/doc/html/index.html. Экстраполировать отсутствие естественных фич в языке многоэтажным метапрограммированием — это как-то не очень.
Конечно, если я СЛУЧАЙНО поменяю порядок строк с r и m местами, будет беда. Захотеться случайно менять этот порядок, если я внесу его в конструктор, уже не должно. Попробуйте.
Мне кажется, очень уж самонадеянно и высоколобо было отдавать именно порядок инициализации и деинициализации на роль «RAII для класса как scope».
Я понимаю, что RAII, copy elision и пр. полагаются на фундаментальное свойство. Я не могу ответить, как. Я хотел бы иметь этот ответ, чтобы объяснить изучающим С++: «это так, ПОТОМУ ЧТО вот», а не просто «потому что». Пока не могу. Может быть, поможете :)
Конкретно, вот например RAII. Почему ИМЕННО ему важен детерминированный порядок инициализации и разрушения?
Хочется показать 2-3 примерами, что фундаментальное свойство — так его и назовём — не вещь в себе, а требование корневых механизмов С++.
Мы не должны мириться с тем, что комитет загнал в угол сам себя.
По сути: кому помогал тот факт, что поля класса инициализируются в порядке их объявления в классе? Это массовый источник заблуждений. Все ожидают, что они инициализированы так, как ты записал в списке инициализации конструктора, и мне до сих пор приходится себя одергивать, когда я его пишу. То, что порядок инициализации не перегружен порядком, заданным мной к конструкторе, меня удивляет. Можно себя очень глубоко загнать — godbolt.org/z/NpFiI1:
Дальше, список инициализации в конструкторе не требует сохранять порядок как в классе, хотя оператор запятая у нас работает строго слева направо. Почему в списке инициализации можно, а в тэгированной инициализации нельзя? Может быть, комитету стоило поправить это место, и разрешить перегрузку порядка инцициализации с по-классовой к по-списочной?