Лазар Бешкенадзе:
J>>Вообще-то, B() — это и есть явная инициализация.
ЛБ>Вообще-то с самого начала это было просто вызовом default конструктора.
И что дальше с этим B() можно было делать (не меняя ничего в классе B)? Попытка передать его куда-нибудь по ссылке могла бы привести к undefined behavior, т.к. реализация была вправе делать копию объекта при инициализации ссылки на const rvalue-выражением, а копирование неинициализированного скалярного объекта неявно сгенерированным копирующим конструктором A влечёт неопределённое поведение. Попытка снять const с копии и присвоить i какое-нибудь значение тоже бы повлекла неопределённое поведение.
Чтоб хоть как-то поюзать такое выражение, пришлось бы что-то поменять в A или B — например, добавить в B
B &as_lvalue() { return *this; }
чтоб потом гарантированно получить прямой доступ к временному объекту через B().as_lvalue().
ЛБ>Но уже тогда просматривался будущий идиотизм потому что вместо того чтобы int() считать неинициализированным объектом типа int его объявили просто нулем — 0.
По-моему, лучше бы инициализированное состояние было по умолчанию и неинициализированное — по явному требованию, а не наоборот. Наличие или отсутствие избыточной инициализации реально может повлиять на производительность только в каких-то специфичных случаях, а вот баги из-за пропущенной инициализации могут вылезти где угодно.
ЛБ>Вообще-то value initialization была введена только в 2003 но тогда хоть оставили лазейку чтобы от этой медвежьей услуги отказаться.
Стоит помнить, что инициализация нулями — это не более, чем часть формального описания observable behavior; реального же контроля над ходом вычислений в существующих средах исполнения программ стандартизированный C++ никогда не давал. Правила C++ составлялись и продолжают составляться исходя из концепции, подразумевающей, что довольно большую часть решений о том, как именно что-то будет вычисляться, берёт на себя оптимизатор, а не автор кода на C++. В данном случае если оптимизатор способен определить, что инициализировать объект нулями избыточно (в том плане, что такая инициализация не влияет на итоговый результат), то он может лишнюю инициализацию убрать. Видимо, на это и был расчёт, когда вводилось столь навязчивое заполнение нуликами (теперь ими даже padding bits забиваются).
ЛБ>Вообще-то теперь это уже пипец.
Меня больше впечатлило слияние new-выражений в C++14:
void mergeable(int x) {
// These heap allocations are safe for merging:
std::unique_ptr<char[]> a{new (std::nothrow) char[8]};
std::unique_ptr<char[]> b{new (std::nothrow) char[8]};
std::unique_ptr<char[]> c{new (std::nothrow) char[x]};
g(a.get(), b.get(), c.get());
}
Тут оптимизатор может объединить три new-выражения в одно, сделав из этого кода что-то наподобие
void mergeable(int x) {
std::unique_ptr<char[]> a{new (std::nothrow) char[8 + 8 + x]};
char *__b = a.get() ? a.get() + 8 : nullptr;
char *__c = __b ? __b + 8 : nullptr;
g(a.get(), __b, __c);
}
Получается, что из-за невозможности выделить большой кусок памяти, образованный объединением трёх частей, соответствующих отдельным new-выражениям, мы можем получить фэйл для каждого из этих new-выражений, даже если какое-то их непустое подмножество могло бы удачно выделить память частями указанных в программе размеров. Довольно выразительный пример того, как контроль за исполнением программы ускользает из рук программиста.