Элизия (Copy Elision) в C++
Задумался насчёт элизии. Вроде и хорошо, оптимизирует программы... Но а что, если конструктор по умолчанию и конструктор копирования должны по-разному инициализировать объекты? Как например в следующем коде.
#include <iostream>
class A
{
private:
int m_value;
public:
A(int value = 0) : m_value {value} { std::cout << "A(int) -> " << m_value << std::endl; }
A(const A &obj) : m_value {-obj.m_value} { std::cout << "A(const A&) -> " << m_value << std::endl; }
};
int main()
{
A obj1 { 10 };
A obj2 { obj1 };
// obj3.m_value равен 35, а ожидалось -35
A obj3 { A{35} };
return 0;
}
Я у себя получил следующий вывод:
A(int) -> 10
A(const A&) -> -10
A(int) -> 35
Да, вряд ли кто-то станет так инициализировать объект obj3, через анонимный объект, но всё же. Можно ли как-то отключать элизию?
Ответы (2 шт):
Заковырка возникает из-за предположения, что в A obj3 { A{35} }; создается анонимный объект. Создание prvalue в С++ теперь вообще никогда само по себе не приводит к созданию объекта. Если необходимо создать именно анонимный объект, то prvalue можно легко метериализовать до вызова конструктора копирования:
#include <iostream>
#include <utility>
class A
{
private:
int m_value;
public:
A(int value = 0) : m_value {value} { std::cout << "A(int) -> " << m_value << std::endl; }
A(const A &obj) : m_value {-obj.m_value} { std::cout << "A(const A&) -> " << m_value << std::endl; }
};
int main()
{
A obj1 { 10 };
A obj2 { obj1 };
// obj3.m_value равен -35
A obj3 {::std::move(A{35})};
return 0;
}
Можно ли как-то отключать элизию?
У некоторых компиляторов есть флаги для этого. Но поскольку copy elision стала обязательной в C++17, отключив ее, вы будете писать уже не на C++, а на каком-то сомнительном его диалекте.
Поэтому решение только одно - писать нормальные классы, которые не ломаются от убирания лишних копий.