Это мой первый пост. Я полагаю, что знаю лучшие практики по stackoverflow, но, вероятно, не на 100%. Я считаю, что нет конкретного поста, посвященного моему допросу; также я надеюсь, что это не слишком расплывчато.
Я пытаюсь найти хорошие практики для написания конструкторов C++, которые выполняют работу со средними и тяжелыми нагрузками.
Включение (всех?) работы init в списки инициализации кажется хорошей идеей по двум причинам, которые приходят мне в голову, а именно:
Приобретение ресурсов — это инициализация
Насколько я знаю, самый простой способ гарантировать правильную инициализацию членов при получении ресурсов — убедиться, что то, что находится внутри скобок списка инициализации, правильно при оценке.
class A { public: A(const B & b, const C & c) : _c(c) { /* _c was allocated and defined at the same time */ /* _b is allocated but its content is undefined */ _b = b; } private: B _b; C _c; }
const
участников классаИспользование списков инициализации — единственный правильный способ использования
const
элементов, которые могут содержать фактический контент.class A { public: A(int m, int n, int p) : _m(m) /* correct, _m will be initialized to m */ { _n = n; /* incorrect, _n is already initialized to an undefined value */ *(const_cast<int*>(&_p)) = p; /* technically valid, but ugly and not particularly RAII-friendly */ } private: const int _m, _n, _p; }
Однако некоторые проблемы, похоже, влияют на использование списков инициализации:
- #P9#
#P10# #P11#
#P12#
#P13# #P14# #P15# #P16#A(int in) : _n(in), _m(_n) {}
#P17#int in_to_n(int in) { /* ... */ return n; } int modify(int n) { /* ... */ return modified; } A::A(int in) : _n(in_to_n(in)) , _n_modified(modify(in_to_n(in))) {}
Сколько работ вы можете включить в список?
В предыдущем примере я вызывал функции для вычисления того, какими атрибутами должны быть инициализированы. Это могут быть простые/лямбда-функции или статические/нестатические методы текущего класса или другого. (Я не предлагаю использовать нестатические методы текущего класса, это может быть даже неопределенное использование в соответствии со стандартом, не уверен.)
Я предполагаю, что это само по себе не является большой проблемой, но нужно приложить особые усилия для ясности, чтобы сохранить ясность намерения кода при написании больших классов, которые выполняют большую работу таким образом.
Кроме того, когда вы пытаетесь применить решение к предыдущей проблеме, при инициализации вашего экземпляра вы можете сделать очень мало независимой работы... Это обычно становится большим, если у вас есть длинная последовательность атрибутов для инициализации с внутренними зависимостями. Это начинает выглядеть как просто программа, переведенная в список инициализации; Я предполагаю, что это не то, к чему должен переходить С++?
Множественные инициализации
Часто вычисляют две переменные одновременно. Установка двух переменных одновременно в списке инициализации означает:
использование уродливого промежуточного атрибута
struct InnerAData { B b; C c; }; /* must be exported with the class definition (ugly) */ class A { public: A(const D & input) : _inner(work(input)) , _b(_inner.b) , _c(_inner.c) {} private: B _b; C _c; InnerAData _inner; }
Это ужасно и приводит к дополнительным бесполезным копиям.
или какой-то уродливый хак
class A { public: A(const D & input) : _b(work(input)) {} private: B _b; C _c; B work(const D & input) { /* ... work ... */ _c = ...; } }
Это еще более ужасно и даже не работает с
const
или не встроенными атрибутами типа.
хранить вещи
const
Иногда может потребоваться большая часть ctor, чтобы выяснить значение, которое нужно присвоить атрибуту, поэтому проверка того, что это
const
, и, следовательно, перемещение работы в список инициализации, может показаться ограниченным. Я не буду приводить полный пример, но думаю что-то вроде вычисления данных из имени файла по умолчанию, затем вычисления полного имени файла из этих данных, затем проверки существования соответствующего файла для установки константного логического значения и т. д.Я думаю, это не принципиальная проблема, но все это кажется интуитивно более разборчивым в теле ctor, и перемещение его в список инициализации только для правильной инициализации поля const кажется излишним. Может быть, я просто воображаю вещи.
Итак, вот сложная часть: задать конкретный вопрос! Сталкивались ли вы с подобными проблемами, нашли ли вы лучшее решение, если нет, какой урок следует усвоить или я что-то упустил?
Я предполагаю, что моя проблема в том, что я в значительной степени пытаюсь переместить всю работу в список инициализации, когда я мог бы найти компромисс того, какое состояние инициировано, и оставить некоторую работу на потом. Я просто чувствую, что список инициализации мог бы играть большую роль в создании современного кода C++, чем он, но я еще не видел, чтобы они продвигались дальше базового использования.
Кроме того, я действительно не уверен, почему значения инициализируются в этом порядке, а не в порядке списка. Мне устно сказали, что это потому, что атрибуты в стеке упорядочены, и компилятор должен гарантировать, что данные стека никогда не превышают SP. Я не уверен, как это окончательный ответ ... почти уверен, что можно реализовать безопасные произвольно переупорядоченные списки инициализации, поправьте меня, если я ошибаюсь.
const
, ссылки-члены (Class &c;
) тоже должны быть в списках инициализации. - person John Burger   schedule 06.07.2016*(const_cast<int*>(&_p)) = p; /* technically valid,
нет! - person curiousguy   schedule 22.07.2016