Является ли следующий код допустимым C ++ в соответствии со стандартом (не считая … s)?
bool f(T& r)
{
if(...)
{
r = ...;
return true;
}
return false;
}
T x = (f(x) ? x : T());
Известно, что он компилируется в версиях GCC, которые использует этот проект (4.1.2 и 3.2.3 … даже не заводите меня …), но должен Это?
редактироватьЯ добавил некоторые детали, например, как концептуально выглядит f () в исходном коде. В основном, это должно быть инициализировано Икс в определенных условиях.
Синтаксически это так, однако, если вы попробуете это
#include <iostream>
using namespace std;
typedef int T;
bool f(T& x)
{
return true;
}
int main()
{
T x = (f(x) ? x : T());
cout << x;
}
это выводит некоторый случайный мусор.
Однако, если вы измените
bool f(T& x)
{
x = 10;
return true;
}
затем выводит 10.
В первом случае объект x
объявляется, и компилятор назначает какое-то псевдо-произвольное значение (поэтому вы не инициализируете его), тогда как во втором вы специально назначаете значение (T()
т.е. 0
) после объявления, т. е. вы инициализируете его.
Я думаю, что ваш вопрос похож на этот:
Используя вновь объявленную переменную в инициализации (int x = x + 1)?
Он, несомненно, должен компилироваться, но может привести к неопределенному поведению.
T
является не примитивным типом, неопределенным поведением, если оно назначено.T
является примитивным типом, четко определенным поведением, если оно нелокальное, и неопределенным поведением, если оно не назначено перед чтением (за исключением типов символов, где оно определено, чтобы дать неопределенное значение).Соответствующей частью Стандарта является это правило из 3.8, Время жизни объекта:
Время жизни объекта типа
T
начинается, когда:
- хранение с правильным выравниванием и размером для типа T, и
- если объект имеет нетривиальную инициализацию, его инициализация завершена.
Так что жизнь x
еще не началось. В том же разделе мы находим правило, которое регулирует использование x
:
Так же, до начала жизни объекта но после того, как хранилище, которое будет занимать объект, было выделено или после того, как закончился срок службы объекта, и до того, как хранилище, которое занял объект, будет повторно использовано или освобождено, Любое glvalue, которое относится к исходному объекту, может быть использовано, но только ограниченным образом. О строящемся или разрушаемом объекте см. 12.7. Иначе, такое glvalue относится к выделенному хранилищу (3.7.4.2), и использование свойств glvalue, которые не зависят от его значения, является четко определенным. Программа имеет неопределенное поведение, если:
- преобразование lvalue в rvalue (4.1) применяется к такому glvalue,
- glvalue используется для доступа к нестатическому элементу данных или вызывать нестатическую функцию-член объекта, или же
- glvalue связан со ссылкой на виртуальный базовый класс (8.5.3), или
- glvalue используется как операнд dynamic_cast (5.2.7) или как операнд typeid.
Если ваш тип не примитивен, то попытка присвоить его — это вызов T::operator=
, нестатическая функция-член. Фул-стоп, то есть неопределенное поведение в соответствии со случаем 2.
Примитивные типы назначаются без вызова функции-члена, поэтому давайте теперь подробнее рассмотрим раздел 4.1, Преобразование Lvalue-в-значение, чтобы увидеть, когда именно это преобразование lvalue-в-значение будет неопределенным поведением:
Когда преобразование lvalue в rvalue происходит в неоцененном операнде или его подвыражении (раздел 5), значение, содержащееся в ссылочном объекте, не доступно. Во всех остальных случаях результат конвертации определяется по следующим правилам:
- Если
T
есть (возможно резюме квалифицированных)std::nullptr_t
, результатом является константа нулевого указателя (4.10).- В противном случае, если
T
имеет тип класса, преобразование копирует-инициализирует временный типT
от glvalue и результат преобразования является prvalue для временного.- В противном случае, если объект, на который ссылается glvalue, содержит недопустимое значение указателя (3.7.4.2, 3.7.4.3), поведение определяется реализацией.
- В противном случае, если
T
это (возможно резюме квалифицированных) тип знака без знака (3.9.1), и объект, на который ссылается glvalue, содержит неопределенное значение (5.3.4, 8.5, 12.6.2), и этот объект не имеет автоматической продолжительности хранения, или glvalue был операндом одинарный&
оператор или он был привязан к ссылке, результатом является неопределенное значение.- В противном случае, если объект, на который ссылается glvalue, содержит неопределенное значение, поведение не определено.
- В противном случае значение, содержащееся в объекте, указанном в glvalue, является результатом prvalue.
(обратите внимание, что эти правила отражают переписку для будущего стандарта C ++ 14, чтобы их было легче понять, но я не думаю, что здесь есть реальные изменения в поведении)
Ваша переменная x
имеет1 неопределенное значение во время создания ссылки lvalue и ее передачи f()
, Пока эта переменная имеет тип примитива и ее значение присваивается до того, как она будет прочитана (чтение — это преобразование из lvalue в rvalue), код в порядке.
Если переменная не назначена перед чтением, эффект зависит от T
, Типы символов будут вызывать код, который выполняет и использует произвольное, но допустимое значение символа. Все остальные типы вызывают неопределенное поведение.
1 Если не x
имеет статическую продолжительность хранения, например, глобальную переменную. В этом случае он инициализируется нулями перед выполнением, согласно разделу 3.6.2 Инициализация нелокальных переменных:
Переменные со статической продолжительностью хранения (3.7.1) или продолжительностью хранения потока (3.7.2) должны быть инициализированы нулями (8.5) перед любой другой инициализацией.
В этом случае статической длительности хранения невозможно выполнить преобразование lvalue в rvalue неопределенного значения. Но нулевая инициализация не является допустимым состоянием для всех типов, так что будьте осторожны с этим.
Хотя область играет роль, реальная проблема связана с временем жизни объекта, а точнее для объекта с нетривиальная инициализация когда начинается жизнь
Это тесно связано с Может ли инициализирующее выражение использовать саму переменную? а также Допустима ли передача объекта C ++ в его собственный конструктор?. Хотя мои ответы на эти вопросы не дают четкого ответа на этот вопрос, он не выглядит дубликатом.
Ключевой частью проекта стандарта C ++, который нас интересует, является раздел 3.8
[Basic.life] который говорит:
Время жизни объекта является свойством среды выполнения объекта. Говорят, что объект имеет нетривиальную инициализацию
если это класс или агрегатный тип, и он или один из его членов инициализируются конструктором, отличным от тривиального
конструктор по умолчанию. [Примечание: инициализация тривиальным конструктором копирования / перемещения является нетривиальной инициализацией. —
конечная нота] Время жизни объекта типа T начинается, когда:
- хранение с правильным выравниванием и размером для типа T, и
- если объект имеет нетривиальную инициализацию, ее инициализация завершена.
Таким образом, в этом случае мы удовлетворяем первую пулю, хранение было получено.
Вторая пуля, где мы находим проблемы:
Нетривиальный случай инициализации
Мы можем получить основную аргументацию от отчет о дефектах 363 который спрашивает:
И если да, то какова семантика самоинициализации UDT?
Например#include <stdio.h> struct A { A() { printf("A::A() %p\n", this); } A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); } ~A() { printf("A::~A() %p\n", this); } }; int main() { A a=a; }
можно скомпилировать и распечатать:
A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8
и предложенная резолюция была:
3.8 [basic.life] пункт 6 указывает, что ссылки здесь действительны. Разрешается брать адрес объекта класса перед
полностью инициализирован, и разрешено передавать его в качестве аргумента
параметр ссылки, пока ссылка может связываться напрямую.
[…]
Таким образом, до начала жизни объекта мы ограничены в том, что мы можем сделать с объектом. Из отчета о дефекте видно, что ссылка на x
действует до тех пор, пока он связывается напрямую.
Что мы можем сделать, описано в разделе 3.8
(В том же разделе и абзаце отчет о дефекте цитирует) говорит (акцент мой):
Точно так же, до того, как началось время жизни объекта, но после
хранилище, которое будет занимать объект, было выделено или, после
время жизни объекта закончилось и до хранения, которое
занятый объект используется повторно или освобожден, любое значение, которое относится к
Оригинальный объект может быть использован, но только ограниченным образом. Для объекта
в стадии строительства или разрушения, см. 12.7. В противном случае, такой гвоздь
относится к выделенному хранилищу (3.7.4.2) и использует свойства
glvalue, которые не зависят от его значения, четко определены. Программа
имеет неопределенное поведение, если:
преобразование lvalue в rvalue (4.1) применяется к такому glvalue,
glvalue используется для доступа к нестатическому члену данных или вызова нестатической функции-члена
объект илиglvalue связан со ссылкой на виртуальный базовый класс (8.5.3), или
glvalue используется как операнд dynamic_cast (5.2.7) или как операнд typeid.
В вашем случае мы обращаемся к нестатическому члену данных здесь, см. Акцент выше:
r = ...;
Так что если T
имеет нетривиальную инициализацию, тогда эта строка вызывает неопределенное поведение и поэтому чтение из r
который также будет доступ, покрытый в отчет о дефекте 1531.
Если x
имеет статическую продолжительность хранения, он будет инициализирован нулем, но, насколько я могу судить, это не считается его инициализация завершена так как конструктор будет вызываться во время динамической инициализации.
Тривиальный случай инициализации
Если T
имеет тривиальную инициализацию, тогда время жизни начинается после получения памяти и записи в r
хорошо определенное поведение. Хотя учтите что чтение r
перед его инициализацией вызовет неопределенное поведение так как это приведет к неопределенному значению. Если x
имеет статическую продолжительность хранения, то она инициализируется нулями, и у нас нет этой проблемы.
В случае компиляции, в любом случае, вызываете ли вы неопределенное поведение или нет, это позволяет компилироваться. Компилятор не обязан производить диагностику для неопределенного поведения, хотя может. Он обязан только производить диагностику некорректно написанного кода, чего нет ни в одном из проблемных случаев.