Как отключить оптимизацию возвращаемого значения c ++ только для одного типа?

Я сталкивался с ситуацией, когда я действительно необходимо выполнить нетривиальный код в конструкторе копирования / операторе присваивания. От этого зависит правильность алгоритма.

Хотя я мог бы отключить оптимизацию возвращаемого значения с помощью переключателя компилятора, это кажется пустой тратой, потому что это только один тип, для которого мне нужно отключить его, так почему должна страдать производительность всего приложения? (Не говоря уже о том, что моя компания не разрешила бы мне добавить переключатель в любом случае).

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 может. Я знаю, что это расплывчато, но вам придется с этим смириться.

-1

Решение

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

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&) конструктор.

1

Другие решения

Как указал Анджью, вы можете использовать промежуточный тип. Вот пример с некоторыми оптимизациями с использованием 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 — вызывается для использования возвращаемого значения оператора
1

Стандарт 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&,

0