Исключение копирования/перемещения требует явного определения конструкторов копирования/перемещения.

Рассмотрим следующую программу:

#include <iostream>
#include <utility>

class T {
public:
    T() { printf("address at construction:    %zx\n", (uintptr_t)this); }
    // T(const T&) { printf("copy-constructed\n"); } // helps
    // T(T&&) { printf("move-constructed\n"); }      // helps
    // T(const T&) = default;                        // does not help
    // T(T&&) = default;                             // does not help
};

T f() { return T(); }

int main() {
    T x = f();
    printf("address after construction: %zx\n", (uintptr_t)&x);
    return 0;
}

Компиляция с g++ -std=c++17 test.cpp дает следующий результат (то же самое с clang++):

address at construction:    7ffcc7626857
address after construction: 7ffcc7626887

Основываясь на справочнике по C++, я ожидаю, что программа выведет два одинаковых адреса, потому что копирование/перемещение должно гарантированно исключаться (по крайней мере, в С++ 17).

Если я явно определяю либо конструктор копии, либо конструктор перемещения, либо и то, и другое (см. закомментированные строки в примере), программа выдает ожидаемый результат (даже с C++11):

address at construction:    7ffff4be4547
address after construction: 7ffff4be4547

Простая установка конструкторов копирования/перемещения на default не помогает.

В ссылке прямо указано

[Конструкторы копирования/перемещения] не обязательно должны присутствовать или быть доступными

Итак, что мне здесь не хватает?


person Al Gebra    schedule 07.04.2018    source источник
comment
Связанный: поведение гарантированного удаления копии зависит от существования определенного пользователем "> stackoverflow.com/questions/48879226/   -  person xskxzr    schedule 07.04.2018


Ответы (1)


Потому что это особый случай, когда исключение копирования может не применяться.

Цитата из [class.temporary], параграф 3:

Когда объект типа класса X передается в функцию или возвращается из нее, если каждый конструктор копирования, конструктор перемещения и деструктор X либо тривиален, либо удален, и X имеет по крайней мере один неудаленный конструктор копирования или перемещения, реализации разрешено создавать временный объект для хранения параметра функции или объекта результата. Временный объект создается из аргумента функции или возвращаемого значения соответственно, а параметр функции или возвращаемый объект инициализируется, как если бы с помощью неудаляемого тривиального конструктора копировался временный (даже если этот конструктор недоступен или не будет выбран разрешением перегрузки для выполнения копирования или перемещения объекта). [ Примечание: эта свобода предоставляется для того, чтобы объекты типа класса могли быть переданы или возвращены из функций в регистрах. — конец примечания ]

person xskxzr    schedule 07.04.2018
comment
Интересно, однако мне кажется, что стандарт формулирует это исключение слишком широко. Мотивация моего вопроса заключалась в том, что мой T довольно большой, а мой стек очень маленький, поэтому я сильно забочусь о том, чтобы не дублировать объект в стеке. Если этот шаблон встречается несколько раз, добавление явного конструктора копирования в каждый класс кажется хаком, могу ли я что-нибудь сделать в f(), чтобы избежать этого? - person Al Gebra; 07.04.2018
comment
@AlGebra - Но ваш T здесь невелик, он очень мал. Очень маленький объект, созданный в регистре и возвращенный по значению, может вообще не нуждаться в стековом пространстве. И поэтому это может быть более эффективно, чем привлечение механизма RVO, который, по крайней мере, нуждается в некоторой косвенности. - person Bo Persson; 07.04.2018
comment
@AlGebra: перестаньте сомневаться в своем компиляторе. Если ваш фактический T действительно достаточно велик, компилятор не будет выполнять эту оптимизацию. Так что позвольте вашему компилятору делать свою работу, чтобы вы могли вернуться к своей работе: писать работающую программу. - person Nicol Bolas; 07.04.2018
comment
@BoPersson @NicolBolas Спасибо, что указали на это. При добавлении const int data[5] = {0}; к T компилятор действительно игнорирует перемещение. Однако в моем реальном случае объекты фактически дублируются в стеке, поэтому я исследовал это в первую очередь. Я, должно быть, пропустил некоторые другие важные детали, изолируя проблему. - person Al Gebra; 08.04.2018