Почему конструктор копирования не опущен здесь? - PullRequest
6 голосов
/ 27 апреля 2010

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

Это кажется простой возможностью исключить конструктор копирования, поскольку нет никаких побочных эффектов при доступе к значению поля в копии bar 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;
}

Ответы [ 4 ]

11 голосов
/ 27 апреля 2010

Это не один из двух судебных дел об отмене копии, описанных в 12.8 / 15:

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

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

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

2 голосов
/ 27 апреля 2010

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

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

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

Демонстрационный код, когда применимо копирование:

#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.

1 голос
/ 27 апреля 2010

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

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

b.F().a = 5;

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

1 голос
/ 27 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...