Почему конструктор копии не игнорируется здесь?

(Я использую gcc с -O2.)

Это походит на простую возможность игнорировать конструктора копии, так как нет никаких побочных эффектов к доступу к значению поля в a barкопия a foo; но конструктора копии вызывают, так как я получаю вывод meep meep!.

#include <iostream>

struct foo {
  foo(): a(5) { }
  foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; }
  int a;
};

struct bar {
  foo F() const { return f; }
  foo f;
};

int main()
{
  bar b;
  int a = b.F().a;
  return 0;
}
6
задан Jesse Beder 26 April 2010 в 22:35
поделиться

4 ответа

Это не один из двух законных случаев элизии copy ctor, описанных в 12.8/15:

Оптимизация возвращаемого значения (когда автоматическая переменная возвращается из функции, и копирование этой автоматической переменной в возвращаемое значение устраняется путем построения автоматической переменной непосредственно в возвращаемом значении) - нет. f не является автоматической переменной.

Временный инициализатор (когда временное значение копируется в объект, и вместо создания временного значения и его копирования, временное значение создается непосредственно в месте назначения) - нет f тоже не временная. b.F() - временное, но оно никуда не копируется, а просто происходит обращение к члену данных, так что к моменту выхода из F() ускорять уже нечего.

Поскольку ни один из законных случаев элиминирования copy ctor не подходит, а копирование f в возвращаемое значение F() влияет на наблюдаемое поведение программы, стандарт запрещает его элиминировать. Если бы вы заменили печать на какую-то ненаблюдаемую деятельность и изучили сборку, вы могли бы увидеть, что этот конструктор копирования был оптимизирован. Но это было бы по правилу "as-if", а не по правилу исключения конструктора копий.

11
ответ дан 8 December 2019 в 15:59
поделиться

Конструктор копирования вызывается, потому что а) нет гарантии, что вы копируете значение поля без изменений, и б) потому что ваш конструктор копирования имеет побочный эффект (печатает сообщение).

1
ответ дан 8 December 2019 в 15:59
поделиться

Лучше думать об исключении копирования с точки зрения временного объекта. Так это описывается в стандарте. Временное может быть «свернуто» в постоянный объект, если оно скопировано в постоянный объект непосредственно перед его уничтожением.

Здесь вы создаете временный объект в функции return. На самом деле он ни в чем не участвует, поэтому вы хотите, чтобы его пропустили. Но что, если бы вы сделали

b.F().a = 5;

, если бы копия была опущена, и вы работали бы с исходным объектом, вы бы изменили b через не-ссылку.

1
ответ дан 8 December 2019 в 15:59
поделиться

Удаление копии происходит только тогда, когда копия действительно не нужна. В частности, это когда есть один объект (назовите его A), который существует на время выполнения функции, и второй объект (назовите его B), который будет скопирован из первого объекта, и немедленно после этого A будет уничтожен (т.е. при выходе из функции).

В этом очень конкретном случае стандарт разрешает компилятору объединить A и B в два разных способа обращения к одному и тому же объекту. Вместо того, чтобы требовать, чтобы A был создан, затем B был скопирован из A, а затем A был уничтожен, он позволяет A и B рассматривать как два способа ссылки на один и тот же объект, поэтому (один) объект создается как A, и после того, как функция возвращается, называться B, но даже если конструктор копирования имеет побочные эффекты, копию, которая создает B из A, все равно можно пропустить. Также обратите внимание, что в этом случае A (как объект, отдельный от B) также никогда не уничтожается - например, если ваш dtor также имеет побочные эффекты, они также могут (будут) опущены.

Ваш код не соответствует этому шаблону - первый объект не перестает существовать сразу после того, как был использован для инициализации второго объекта.После возврата F () есть два экземпляра объекта. В этом случае оптимизация возвращаемого значения [Named] (также известная как copy elision) просто не применяется.

Демонстрационный код при применении исключения копирования:

#include <iostream>

struct foo {
  foo(): a(5) { }
  foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; }
  int a;
};

int F() { 
    // RVO
    std::cout << "F\n";
    return foo();
}

int G() { 
    // NRVO
    std::cout << "G\n";
    foo x;
    return x;
}

int main() { 
    foo a = F();
    foo b = G();
    return 0;
}

И MS VC ++, и g ++ оптимизируют оба оператора копирования из этого кода с включенной оптимизацией. g ++ оптимизирует и то, и другое, даже если оптимизация отключена. Когда оптимизация отключена, VC ++ оптимизирует анонимный возврат, но использует ctor копирования для именованного возврата.

2
ответ дан 8 December 2019 в 15:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: