Оптимизация количества вызовов конструктора
На работе у нас есть класс с дорогим конструктором, поэтому мы хотели бы, чтобы его вызывали как можно меньше. Мы рассмотрели его использование и попытались сделать код более дружественным RVO, чтобы сказать.
Однако мы обнаружили причуду в компиляторе g++, где мы не понимали, что произошло.
Рассмотрим две реализации оператора +
const Imaginary Imaginary::operator+(const Imaginary& rhs) const
{
Imaginary tmp(*this);
tmp.append(rhs);
return tmp;
}
и
const Imaginary Imaginary::operator+(const Imaginary& rhs) const
{
return Imaginary(*this).append(rhs);
}
Я поставил распечатки в различных конструкторах и со следующей небольшой программой
int main(int argc, char* argv[])
{
Imaginary x(1, 1);
Imaginary y(2, 1);
Imaginary c = x + y;
return 0;
}
Я получаю эту распечатку с первой реализацией оператора +
int/int ctor
int/int ctor
Copy ctor
И я получаю следующее, когда используется второй вариант operator +
int/int ctor
int/int ctor
Copy ctor
Copy ctor
Здесь мы видим, что g++ может оптимизировать один вызов конструктора копирования в одном случае, но не последним, и, к моему удивлению, ему удалось сделать это с помощью более неуклюжей реализации, где я сохранил его во временное.
Теперь я мог бы понять это больше, если это было наоборот, но, видимо, это не
и теперь я надеюсь, что, может быть, вы могли бы просветить меня по этому вопросу.
Я должен, вероятно, добавить, что когда мы добавляем --no-elide-constructors в качестве флага в g++
Я получаю следующую распечатку
int/int ctor
int/int ctor
Copy ctor
Copy ctor
Copy ctor
С уважением, Маттиас
Ответы
Ответ 1
Если компилятор не может встроить append
, то он не может определить, что возвращаемое значение является целевым объектом. Тогда он не знает, что временное возвращается, и не может создать его на месте.
У вас будет такое же поведение:
Imaginary tmp(*this);
return tmp.append(rhs);
Если возвращаемое значение append
непрозрачно для компилятора (определенное в другом модуле компиляции), оно предотвращает оптимизацию.
Ответ 2
Стефан Т. Лававей упоминает
http://channel9.msdn.com/Events/GoingNative/2013/Don-t-Help-the-Compiler
что (N) RVO происходит только тогда, когда тип возвращаемого значения точно такой же, как тип, возвращаемый из метода.
Например:
string foo() {string tmp; return tmp;} // Same type, uses NRVO or automatic move.
string foo() {const string& tmp = "bar"; return tmp;} // Types differ, no NRVO, nor automatic move.
string foo() {string tmp; string& ref = tmp; return ref;} // Types differ, no NRVO, nor automatic move.
string foo() {string tmp; return (string&) tmp;} // Types differ, no NRVO, nor automatic move.
(cf. http://coliru.stacked-crooked.com/a/79e79e5bb0350584)
Я думаю, append
возвращает ссылку на Imaginary, а поскольку Imaginary&
не имеет тот же тип, что и Imaginary
, это предотвращает (N) RVO.