Условное перемещение или копирование присваивания в троичном операторе

Для следующего фрагмента кода:

#include <utility>
#include <iostream>

#define C(name) (name ? name : "nullptr")
#define PP { std::cout << __PRETTY_FUNCTION__ << " : " << C(name) << '\n'; }
#define T { std::cout << __PRETTY_FUNCTION__ << " : " << C(name) << " -> " << C(rhs.name) << '\n'; }

struct A
{
    const char * name = nullptr;
    A(const char * name) : name{name} PP
    A(A && rhs) : name{std::exchange(rhs.name, nullptr)} PP
    A(const A & rhs) : name{rhs.name} PP
    A & operator = (A && rhs) { T; std::swap(name, rhs.name); return *this; }
    A & operator = (const A && rhs) { T; name = rhs.name; return *this; }
    ~A() PP
};

#include <random>

int main()
{
    std::random_device d;
    A a{"a"};
    A b{"b"};
    A c{"c"};
    std::cout << "begin\n";
    a = ((d() % 2) == 0) ? b : std::move(c);
    std::cout << "end\n";
}

возможны два следующих выхода:

A::A(const char*) : a
A::A(const char*) : b
A::A(const char*) : c
begin
A::A(A&&) : c
A& A::operator=(A&&) : a -> c
A::~A() : a
end
A::~A() : nullptr
A::~A() : b
A::~A() : c

а также

A::A(const char*) : a
A::A(const char*) : b
A::A(const char*) : c
begin
A::A(const A&) : b
A& A::operator=(A&&) : a -> b
A::~A() : a
end
A::~A() : c
A::~A() : b
A::~A() : b

Возможно ли (в соответствии со Стандартом), чтобы компилятор избегал использования временного значения во время назначения копирования / перемещения с троичным оператором в вышеприведенном случае и отправлял оператор копирования или перемещения для назначения значения правой части (b или же c) прямо на левую сторону (aв зависимости от состояния?

2 ответа

Возможно ли... чтобы компилятор избегал использования временного значения во время назначения копирования / перемещения с троичным оператором

Это интересный вопрос из-за того, как вы написали код.

В общем да. Компилятору разрешается переставлять или исключать код при условии, что наблюдаемый результат такой же, как если бы он выполнил код. Это называется правилом "как будто".

Кроме того, компилятору разрешается исключать копии в других обстоятельствах, даже если будет наблюдаться изменение в наблюдаемом поведении, например, RVO (оптимизация возвращаемого значения).

Однако в вашем случае все конструкторы имеют наблюдаемое поведение, которое нельзя изменить - они испускают символы в стандартный вывод!

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

Если мы посмотрим на раздел об удалении при копировании / перемещении из проекта стандарта, http://eel.is/c++draft/class.copy.elision, в котором описываются случаи, когда копирование / перемещение может быть исключено, даже если они имеют побочные эффекты. Мы не видим ни одного случая, который бы охватывал ваш пример:

При соблюдении определенных критериев реализация может опустить конструкцию копирования / перемещения объекта класса, даже если конструктор, выбранный для операции копирования / перемещения и / или деструктор для объекта, имеет побочные эффекты. В таких случаях реализация рассматривает источник и цель пропущенной операции копирования / перемещения просто как два разных способа обращения к одному и тому же объекту. Если первый параметр выбранного конструктора является rvalue ссылкой на тип объекта, уничтожение этого объекта происходит, когда цель будет уничтожена; в противном случае уничтожение происходит в более поздние времена, когда два объекта были бы уничтожены без оптимизации.119 Такое исключение операций копирования / перемещения, называемое разрешением копирования, допускается в следующих обстоятельствах (которые могут быть объединены для устранения нескольких копии):

  • в операторе возврата в функции с типом возврата класса, когда выражение является именем энергонезависимого автоматического объекта (кроме параметра функции или переменной, введенной в объявлении исключения обработчика (13.3)) с помощью тот же тип (игнорируя квалификацию cv), что и тип возврата функции, операция копирования / перемещения может быть опущена путем создания автоматического объекта непосредственно в объекте возврата вызова функции
  • в выражении throw (7.6.17), когда операндом является имя энергонезависимого автоматического объекта (кроме параметра функции или предложения catch), область которого не выходит за пределы самого внутреннего включающего блока try (если он есть), операция копирования / перемещения из операнда в объект исключения (13.1) может быть опущена путем создания автоматического объекта непосредственно в объект исключения
  • когда объявление исключения в обработчике исключений (раздел 13) объявляет объект того же типа (за исключением cv-квалификации), что и объект исключения (13.1), операцию копирования можно опустить, обрабатывая объявление исключения как псевдоним для объекта исключения, если значение программы не изменится, за исключением выполнения конструкторов и деструкторов для объекта, объявленного объявлением исключения. [Примечание: не может быть перемещения из объекта исключения, потому что это всегда lvalue. —Конечная записка]

Исключение копирования требуется, когда выражение оценивается в контексте, требующем постоянного выражения (7.7) и при постоянной инициализации (6.8.3.2). [Примечание: исключение копирования может не выполняться, если такое же выражение вычисляется в другом контексте. —Конечная записка]

Другие вопросы по тегам