Игнорирование const *Base = &Base();
, которое принимает адрес временного, и в большинстве случаев приведет к неопределенному поведению, так как в конце выражения временный объект будет уничтожен, и любой разыменование указателя будет UB
Когда вы пытаетесь связать постоянную ссылку с временным, язык заявляет, что операция (семантически) заключается в том, чтобы скопировать временную переменную в неназванную переменную и затем связать ссылку на эту переменную.
const type& r = f(); // where f() returns a type (not a reference)
эквивалентно:
const type __tmp = f(); // __tmp variable created by the compiler
const type& r = __tmp;
Это объясняет, почему в первом случае, поскольку конструктор копирования недоступен, компилятору не разрешено создавать переменную __tmp
(const type __tmp = f()
не компилируется), и он сообщает вам.
Теперь стандарт позволяет компилятору исключать копии переменных, и, в частности, в этой строке компилятору разрешается помещать __tmp
и результат f()
в точно такое же место в памяти 1 и избегайте выполнения копирования. Во втором случае компилятор проверил, разрешено ли копирование (доступен конструктор копирования), но оптимизировал удаление копии и, таким образом, не вызывает функцию.
Почему он считает, что конструктор доступен, даже если он не был определен? Ну, это часть отдельной модели компиляции, компилятор, принимая решение о том, действителен конструктор или нет, проверяет только текущий блок перевода и не знает, будет ли конструктор копирования доступен в другом TU. , Поскольку копия удаляется, в двоичном файле не вызывается конструктор копирования, и компоновщик не должен разрешать этот символ, поэтому он компилируется и корректно связывается.
1 Для получения более подробной информации и вне рамок стандарта большинство компиляторов (все, что мне известно) реализуют возврат по значению большого объекта (тот, который не помещается в регистр) с помощью передавая скрытый указатель на функцию, вызывающий резервирует место для __tmp
в локальном стеке и передает этот указатель на f()
. При таком соглашении о вызовах возвращаемый объект в этом случае вообще не существует, даже если бы он был использован, если вызов не использовался для инициализации нового объекта.
В случае в примере, где возвращаемый тип помещается в регистры, многие компиляторы сохранят полученное значение в регистре перед возвратом. Это именно та ситуация, когда копирование (концептуально) должно быть выполнено, так как вы не можете привязать ссылку к регистру , но снова вызов конструктора копирования исключается, и компилятор просто сохраняет регистр в расположении __tmp
.