Почему копия ctor используется в этом коде? - PullRequest
10 голосов
/ 28 мая 2009
class A
{
 public:
  A(const int n_);
  A(const A& that_);
  A& operator=(const A& that_);
};

A::A(const int n_)
{ cout << "A::A(int), n_=" << n_ << endl; }

A::A(const A& that_)    // This is line 21
{ cout << "A::A(const A&)" << endl; }

A& A::operator=(const A& that_)
{ cout << "A::operator=(const A&)" << endl; }

int foo(const A& a_)
{ return 20; }

int main()
{
  A a(foo(A(10)));    // This is line 38
  return 0;
}

Выполнение этого кода дает o / p:

A :: A (int), n_ = 10
A :: A (int), n_ = 20

Очевидно, конструктор копирования никогда не вызывается.

class A
{
 public:
  A(const int n_);
  A& operator=(const A& that_);
 private:
  A(const A& that_);
};

Однако, если мы сделаем его закрытым, произойдет ошибка компиляции:

Test.cpp: в функции int main ():
Test.cpp: 21: ошибка: ‘A :: A (const A &)’ является закрытой
Test.cpp: 38: ошибка: в этом контексте

Почему компилятор жалуется, когда он фактически не использует конструктор копирования?
Я использую gcc версии 4.1.2 20070925 (Red Hat 4.1.2-33)

Ответы [ 8 ]

12 голосов
/ 28 мая 2009

Дефект сердечника 391 объясняет проблему.

По сути, текущий стандарт C ++ требует наличия конструктора копирования при передаче временного типа класса в константную ссылку.

Это требование будет удалено в C ++ 0x.

Логика, лежащая в основе требования конструктора копирования, исходит из этого случая:

C f();
const C& r = f(); // a copy is generated for r to refer to
5 голосов
/ 28 мая 2009

Стандарт 2003 года, в §12.2 / 1, гласит:

Даже когда создание временный объект избегается (12.8), все семантические ограничения должны быть уважаемый, как будто временный объект был создан. [Пример: даже если конструктор копирования не вызывается, все семантические ограничения, такие как доступность (пункт 11), довольный. ]

Есть похожие примеры. Исходя из того, что я понял, компилятор может генерировать временные файлы или оптимизировать их.

3 голосов
/ 28 мая 2009

Насколько я вижу, вы нигде не используете конструктор копирования. В операторе foo(A(10)) вы создаете временный объект класса A и передаете его как const-ссылку на foo. Foo возвращает целое число, которое используется при построении объекта a. Следовательно, я не вижу, где здесь задействуется конструктор копирования и как NRVO входит в картину. Кроме того, я скомпилировал следующий код, сделав конструктор копирования закрытым, и он прекрасно скомпилировался в VS2008.

using namespace std;

class A
{
 public:
  A(const int n_);
 private:
  A(const A& that_);
  A& operator=(const A& that_);
};

A::A(const int n_)
{ cout << "A::A(int), n_=" << n_ << endl; }

A::A(const A& that_)    // This is line 21
{ cout << "A::A(const A&)" << endl; }

A& A::operator=(const A& that_)
{ 
    cout << "A::operator=(const A&)" << endl; 
    return *this;
}

int foo(const A& a_)
{ return 20; }


int main(int argc,char *argv[])
{
   A a(foo(A(10)));    // This is line 38
  return 0;

}   
2 голосов
/ 28 мая 2009

Еще одно замечание: компилятор делает другое при работе с временным. Так что речь идет не о конструкторе копирования, а о промежуточном временном.

A original(10);
foo( original ); // does compile
foo( A(10) ); // doesn't compile - needs a copy constructor
1 голос
/ 28 мая 2009

В выражении:

A a(foo(A(10)));

Результатом подвыражения A(10) является rvalue типа A. (5.2.3 [expr.type.conv])

При инициализации константной ссылки из rvalue компилятор может создать временное значение из rvalue и связать его со ссылкой. Даже если он решит не делать этого, конструктор копирования должен быть доступен. (8.5.3 [decl.init.ref]) Этого не произошло бы, если бы ссылка была инициализирована из , совместимого со ссылкой lvalue , где прямое связывание является обязательным. *

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

foo возвращает целое число, поэтому здесь нет копии A.

a инициализируется напрямую из int, возвращаемого foo, поэтому здесь нет копии A.

1 голос
/ 28 мая 2009

Конструктор копирования не используется, но для компиляции кода должен быть доступен конструктор копирования.

РЕДАКТИРОВАТЬ: компилятор Comeau C ++ сообщает следующее:

Comeau C/C++ 4.3.10.1 (Oct  6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing.  All rights reserved.
MODE:strict errors C++ noC++0x_extensions

"ComeauTest.c", line 38: error: "A::A(const A &)" (declared at line 17), required
          for copy that was eliminated, is inaccessible
    A a(foo(A(10)));    // This is line 38
            ^

1 error detected in the compilation of "ComeauTest.c".

Обратите внимание: если включены расширения C ++ 0x, он прекрасно компилируется в компиляторе Comeau C ++.

0 голосов
/ 28 мая 2009

При звонке:

foo( A(10) );

временный объект создается во время жизни вызова. Конструктор копирования используется для заполнения данных. Временный объект удаляется после выполнения вызова.

При звонке:

{ 
  A original(10);
  foo( original ); 
}

Оригинал сбрасывается после выхода из блока. Его можно смело использовать в качестве параметра.

Для оптимальной скорости передайте объект по ссылке, используя временную переменную, которая будет отброшена компилятором во время оптимизации.

0 голосов
/ 28 мая 2009

В общем, вам не следует беспокоиться о том, когда и когда вызывается конструктор копирования. Стандарт C ++ довольно расслаблен, когда вызовы конструктора копирования будут удалены или добавлены. Если ваш класс логически нуждается в этом, предоставьте его (и не забывайте, деструктор и оператор присваивания) - разумное правило.

...