Обновить
25

Пользователь

18
Подписчики
Отправить сообщение
Ну хотя бы:
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, вы же понимаете. Разве не стоит сделать что-то типа:

#include <memory>

struct Resource {};
struct Mutex { Resource& r; Mutex(Resource& r) : r{r} {} };

class Owner {
protected:
  std::unique_ptr<Resource> r { std::make_unique<Resource>() };
  std::unique_ptr<Mutex> m { std::make_unique<Mutex>(*r) };
public:
  ~Owner() { m.reset(); r.reset(); } 
};

int main()
{
    Owner o;
}


Конечно, если я СЛУЧАЙНО поменяю порядок строк с 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;
}

Дальше, список инициализации в конструкторе не требует сохранять порядок как в классе, хотя оператор запятая у нас работает строго слева направо. Почему в списке инициализации можно, а в тэгированной инициализации нельзя? Может быть, комитету стоило поправить это место, и разрешить перегрузку порядка инцициализации с по-классовой к по-списочной?
Вот что говорят stackoverflow.com/questions/53250463/why-c20-doesnt-support-out-of-order-designated-initializer:
In C++, members are destroyed in reverse construction order and the elements of an initializer list are evaluated in lexical order, so field initializers must be specified in order.

Конечно, понятно. По мне, это просто делает нововведение бесполезным. Так можно было бы эмулировать тэгированные параметры функций как в C# — завести параметры по умолчанию в середине, не запоминать порядок аргументов. А тут такая шляпа.
Про move-семантику стоило бы упомянуть, ну и об этом вот: www.youtube.com/watch?v=PNRju6_yn3o.
В С не нужно соблюдать порядок элементов, то есть в нашем примере можно сначала инициализировать с, а потом а. В С++ так делать нельзя, поскольку вещи конструируются в порядке, в котором они объявлены.

Который день сижу и плАчу.
Низкий поклон за эту работу, ребята. Ощущение при просмотре, что акценты расставлены очень релевантно.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность