Подтвердить что ты не робот

Оптимизация количества вызовов конструктора

На работе у нас есть класс с дорогим конструктором, поэтому мы хотели бы, чтобы его вызывали как можно меньше. Мы рассмотрели его использование и попытались сделать код более дружественным 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

С уважением, Маттиас

4b9b3361

Ответ 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.