Я сталкивался с ситуацией, когда я действительно необходимо выполнить нетривиальный код в конструкторе копирования / операторе присваивания. От этого зависит правильность алгоритма.
Хотя я мог бы отключить оптимизацию возвращаемого значения с помощью переключателя компилятора, это кажется пустой тратой, потому что это только один тип, для которого мне нужно отключить его, так почему должна страдать производительность всего приложения? (Не говоря уже о том, что моя компания не разрешила бы мне добавить переключатель в любом случае).
struct A {
explicit A(double val) : m_val(val) {}
A(const A& other) : m_val(other.m_val) {
// Do something really important here
}
A& operator=(const A& other) {
if (&other != this) {
m_val = other.m_val;
// Do something really important here
}
return *this;
}
double m_val;
};
A operator+(const A& a1, const A& a2) {
A retVal(a1.m_val + a2.m_val);
// Do something else important
return retVal;
}
// Implement other operators like *,+,-,/ etc.
Этот класс будет использоваться как таковой:
A a1(3), a2(4), a3(5);
A a4 = (a1 + a2) * a3 / a1;
Оптимизация возвращаемого значения означает, что a4 не будет создан с помощью конструктора копирования, и «действительно важная вещь» не происходит!
Я знаю, что мог бы взломать решение, где operator + возвращает другой тип (скажем, B) и иметь конструктор A, который принимает B в качестве входных данных. Но тогда количество операторов, которые нужно реализовать, взрывается:
B operator+(const A& a1, const A& a2);
B operator+(const B& a1, const A& a2);
B operator+(const A& a1, const B& a2);
B operator+(const B& a1, const B& a2);
Там должно быть лучшее решение. Как я могу взломать его, чтобы RVO не произошло для моего типа? Я могу только изменить код класса А и операторов. Я не могу изменить код вызывающего сайта; то есть я не могу сделать это:
A a1(3), a2(4), a3(5);
A a4;
a4 = (a1 + a2) * a3 / a1;
Одна вещь, которую я рассмотрел, — это попытаться поэкспериментировать с конструкторами перемещения C ++ 11, но я не уверен, что это сработает, и мне не нравится, что это недопустимо в C ++ 03.
Есть идеи?
РЕДАКТИРОВАТЬ: Пожалуйста, просто примите, что это единственный способ, которым я могу сделать то, что мне нужно сделать. Я не могу просто «изменить дизайн». Код вызова исправлен, и я должен реализовать свою стратегию внутри математических операторов и конструктора копирования & оператор присваивания. Идея состоит в том, что на промежуточные значения, вычисленные в уравнении «a4 = (a1 + a2) * a3 / a1», нельзя ссылаться где-либо еще в программе — но a4 может. Я знаю, что это расплывчато, но вам придется с этим смириться.
Отвечая на мой собственный вопрос здесь: я собираюсь укусить пулю и использовать промежуточный тип:
struct B;
struct A
{
A(int i) : m_i(i) {}
A(const B& a);
A(const A& a) : m_i(a.m_i)
{
std::cout << "A(const A&)" << std::endl;
}
int m_i;
};
struct B
{
B(int i) : m_i(i) {}
int m_i;
};
A::A(const B& a) : m_i(a.m_i)
{
std::cout << "A(const B&)" << std::endl;
}
B operator+(const A& a0, const A& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "A+A" << std::endl;
return b;
}
B operator+(const B& a0, const A& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "B+A" << std::endl;
return b;
}
B operator+(const A& a0, const B& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "A+B" << std::endl;
return b;
}
B operator+(const B& a0, const B& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "B+B" << std::endl;
return b;
}
int main()
{
A a(1);
A b(2);
A c(3);
A d = (a+b) + (a + b + c);
}
Вывод на GCC 4.2.1:
A+A
B+A
A+A
B+B
A(const B&)
И я могу сделать «очень важную вещь» в A (const B&) конструктор.
Как указал Анджью, вы можете использовать промежуточный тип. Вот пример с некоторыми оптимизациями с использованием ctor-хода.
#include <utility>
#include <iostream>
struct B;
struct A {
explicit A(double val) : m_val(val)
{
std::cout << "A(double)" << std::endl;
}
A(A&& p) : m_val(p.m_val)
{ /* no output */ }
A(const A& other) : m_val(other.m_val) {
// Do something really important here
std::cout << "A(A const&)" << std::endl;
}
A& operator=(const A& other) {
if (&other != this) {
m_val = other.m_val;
// Do something really important here
std::cout << "A::operator=(A const&)" << std::endl;
}
return *this;
}
double m_val;
A(B&&);
};
struct B
{
operator A const&() const
{
std::cout << "B::operator A const&()" << std::endl;
return a;
}
private:
friend struct A;
A a;
// better: befriend a factory function
friend B operator+(const A&, const A&);
friend B operator*(const A&, const A&);
friend B operator/(const A&, const A&);
B(A&& p) : a( std::move(p) )
{ /* no output */ }
};
A::A(B&& p) : A( std::move(p.a) )
{
std::cout << "A(B&&)" << std::endl;
}
B operator+(const A& a1, const A& a2) {
std::cout << "A const& + A const&" << std::endl;
A retVal(a1.m_val + a2.m_val);
// Do something else important
return std::move(retVal);
}
B operator*(const A& a1, const A& a2) {
std::cout << "A const& * A const&" << std::endl;
A retVal(a1.m_val * a2.m_val);
// Do something else important
return std::move(retVal);
}
B operator/(const A& a1, const A& a2) {
std::cout << "A const& / A const&" << std::endl;
A retVal(a1.m_val / a2.m_val);
// Do something else important
return std::move(retVal);
}
int main()
{
A a1(3), a2(4), a3(5);
A a4 = (a1 + a2) * a3 / a1;
}
IIRC, временное возвращение, скажем, a1 + a2
длится для всей инициализации копии (точнее: для всего полного выражения, и это включает в себя AFAIK конструкцию a4
).
Вот почему мы можем вернуть A const&
изнутри B
хотя B
объекты создаются только как временные.
(Если я ошибаюсь, посмотрите мои предыдущие изменения для некоторых других решений ..: D)
Суть этого примера — комбинация промежуточного типа, движущихся кортов и указанного возврата ссылки.
Вывод g ++ 4.6.3 и clang ++ 3.2:
A(double) <---- A a1(3);
A(double) <---- A a2(4);
A(double) <---- A a3(5);
A const& + A const& <---- a1 + a2;
A(double) <-- A retVal(a1.m_val + a2.m_val);
B::operator A const&()<---- __temp__ conversion B --> const A&
A const& * A const& <---- __temp__ * a3;
A(double) <-- A retVal(a1.m_val * a2.m_val);
B::operator A const&()<---- __temp__ conversion B --> const A&
A const& / A const& <---- __temp__ / a1;
A(double) <-- A retVal(a1.m_val / a2.m_val);
A(B&&) <---- A a4 = __temp__;
Теперь, когда операции копирования и перемещения (которые не показаны) разделены, я думаю, что вы можете реализовать свое «что-то важное» более точно там, где оно принадлежит:
A(double)
— создание нового A
объект из числовых значенийA(A const&)
— фактическая копия A
объект; здесь не бываетA(B&&)
— строительство A
объект из результата оператораB(A&&)
— вызывается для возвращаемого значения оператораB::operator A const&() const
— вызывается для использования возвращаемого значения оператораСтандарт RVO разрешен в следующих случаях ([class.copy] §31, в котором перечислены только применимые части):
в операторе возврата в функции с типом возврата класса, когда выражение является именем энергонезависимого автоматического объекта (другое
чем функция или параметр catch-clause) с тем же cv-unqualified
введите в качестве типа возврата функции, операция копирования / перемещения может быть
опускается путем создания автоматического объекта непосредственно в
возвращаемое значение функциикогда временный объект класса, который не был связан со ссылкой (12.2), будет скопирован / перемещен в объект класса с тем же
cv-unqualified type, операция копирования / перемещения может быть опущена
строительство временного объекта непосредственно в цель
опущено копирование / перемещение
В вашем коде:
A operator+(const A& a1, const A& a2) {
A retVal(a1.m_val + a2.m_val);
// Do something else important
return retVal;
}A a4 = (a1 + a2) * a3 / a1;
участвуют две допустимые копии: копирование revVal
во временный объект, хранящий возвращаемое значение operator+
и копирование этого временного объекта в a4
,
Я не вижу способа предотвратить удаление второй копии (из возвращаемого значения в a4
), но «энергонезависимая» часть стандарта заставляет меня поверить, что это должно предотвратить выпуск первой копии:
A operator+(const A& a1, const A& a2) {
A retVal(a1.m_val + a2.m_val);
// Do something else important
volatile A volRetVal(retVal);
return volRetVal;
}
Конечно, это означает, что вам придется определить дополнительный конструктор копирования для A
принятие const volatile A&
,