Почему конструктор копирования не вызывается в этом случае? - PullRequest
11 голосов
/ 18 ноября 2009

Вот небольшой фрагмент кода:

class A
{
public:
    A(int value) : value_(value)
    {
        cout <<"Regular constructor" <<endl;
    }

    A(const A& other)   : value_(other.value_)  
    {
        cout <<"Copy constructor" <<endl;
    }

private:
    int value_;
};
int main()
{
    A a = A(5);
}

Я предполагал, что вывод будет "Regular Constructor" (для RHS), а затем "Copy constructor" для LHS. Поэтому я избегал этого стиля и всегда объявлял переменную класса как A a(5);. Но к моему удивлению в приведенном выше коде конструктор копирования никогда не вызывается (Visual C ++ 2008)

Кто-нибудь знает, является ли это поведение результатом оптимизации компилятора или некоторой документированной (и переносимой) функцией C ++? Спасибо.

Ответы [ 4 ]

14 голосов
/ 18 ноября 2009

Из другого комментария: «Поэтому по умолчанию я не должен полагаться на него (поскольку это может зависеть от компилятора)»

Нет, это практически не зависит от компилятора. Любой компилятор, стоящий песчинку, не будет тратить время на создание A, а затем копировать его.

В стандарте прямо говорится, что вполне допустимо, чтобы T = x; было эквивалентно высказыванию T(x);. (§12.8.15, стр. 211) Выполнение этого с T(T(x)) явно избыточно, поэтому оно удаляет внутреннее T.

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

A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
7 голосов
/ 30 марта 2012

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

Оператор вида A a = A(5) называется copy-initialization переменной a. Стандарт C ++ 11, 8.5 / 16 гласит:

Выбранная функция вызывается с выражением инициализатора как его аргумент; если функция является конструктором, вызов инициализирует временный для cv-неквалифицированной версии типа назначения. временное является prvalue. Результат вызова (который является временным для случая конструктора) затем используется для прямой инициализации, в соответствии с по приведенным выше правилам, объект, который является пунктом назначения копирования инициализации. В некоторых случаях реализация разрешена устранить копирование, присущее этой прямой инициализации построение промежуточного результата непосредственно в объекте инициализируется; см. 12.2, 12.8 .

Это означает, что компилятор ищет соответствующий конструктор для обработки A(5), создает временный файл и копирует этот временный файл в a. Но при каких обстоятельствах копия может быть удалена?

Посмотрим, что говорит 12.8 / 31:

При выполнении определенных критериев реализация может быть опущена конструкция копирования / перемещения объекта класса, даже если копирование / перемещение Конструктор и / или деструктор для объекта имеют побочные эффекты. В В таких случаях реализация рассматривает источник и цель опущена операция копирования / перемещения просто как два разных способа обращения к тому же объекту, и разрушение этого объекта происходит на позднее, когда два объекта были бы уничтожены без оптимизации. Это исключение операций копирования / перемещения, копия elision , допускается при следующих обстоятельствах (которые могут быть объединены для устранения нескольких копий):

[...]

  • , когда временный объект класса, который не был связан со ссылкой (12.2), будет скопирован / перемещен для объекта класса с тем же типом cv-unqualified операция копирования / перемещения может быть пропущено путем создания временного объекта непосредственно в цель пропущенного копирования / перемещения

Имея все это в виду, вот что происходит с выражением A a = A(5):

  1. Компилятор видит объявление с инициализацией копирования
  2. Конструктор A(int) выбран для инициализации временного объекта
  3. Поскольку временный объект не связан со ссылкой, и он имеет тот же тип A, что и тип назначения в выражении инициализации копирования, компилятору разрешено напрямую создавать объект в a, исключая временный
4 голосов
/ 18 ноября 2009

Здесь у вас есть копия-инициализация из a из временного A(5). Реализация позволила пропустить вызывающий конструктор копирования здесь в соответствии со стандартом C ++ 12.2 / 2.

0 голосов
/ 19 ноября 2009
A a = A(5);

Эта строка эквивалентна

A a(5);

Несмотря на внешний вид в стиле функции, первая строка просто создает a с аргументом 5. Копирование или временные операции не выполняются. Из стандарта C ++, раздел 12.1.11:

Функциональное преобразование типа записи (5.2.3) может использоваться для создания новых объектов этого типа. [ Обратите внимание синтаксис выглядит как явный вызов конструктора. —Конечная записка]

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