Почему в этом коде используется копия ctor?

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 (число), n_ = 10
A :: A (число), 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: error: 'A :: A (const A &)' является закрытым
Test.cpp: 38: error: внутри этот контекст

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


person AngryWhenHungry    schedule 28.05.2009    source источник


Ответы (8)


Основной дефект 391 объясняет проблему.

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

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

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

C f();
const C& r = f(); // a copy is generated for r to refer to
person James Hopkin    schedule 28.05.2009
comment
К вашему сведению, я думаю, что это все еще присутствует в C ++ 11 (12.2/1). - person Lightness Races in Orbit; 09.02.2012

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

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

Подобные примеры есть. Насколько я понимаю, компилятор может создавать временные библиотеки или оптимизировать их.

person aib    schedule 28.05.2009
comment
Но как семантика foo (const A &) диктует использование конструктора копирования? - person xtofl; 28.05.2009

Насколько я понимаю, вы нигде не используете конструктор копирования. В операторе foo(A(10)) вы создаете временный объект класса A и передаете его как константную ссылку на 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;

}   
person Naveen    schedule 28.05.2009
comment
@AngryWhenHungry: Кстати, вам не хватало return * this в операторе присваивания. Я надеюсь, что это не та ошибка компилятора, которую вы получали :-) - person Naveen; 28.05.2009
comment
Посмотрите ответ Джеймса Хопкина: stackoverflow.com/questions/919701/. По сути, для привязки временного объекта к константной ссылке still требуется ctor копирования (в C ++ 03). - person Richard Corden; 28.05.2009

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

A original(10);
foo( original ); // does compile
foo( A(10) ); // doesn't compile - needs a copy constructor
person xtofl    schedule 28.05.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 возвращает int, поэтому здесь нет копии A.

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

person CB Bailey    schedule 28.05.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 ++.

person dalle    schedule 28.05.2009
comment
Какого черта Комо нужно для копирования временного объекта A? Передано по ссылке! - person xtofl; 28.05.2009
comment
А о какой оптимизации возвращаемого значения вы имеете в виду? - person xtofl; 28.05.2009
comment
Нет Объект возвращается, поэтому RVO не имеет отношения к использованию конструктора копирования. Тем не менее, A (10) является rvalue типа A, и компилятор может принять его копию для привязки к параметру ссылки. Именно эта потенциальная копия требует, чтобы конструкторы копирования были доступны. - person CB Bailey; 28.05.2009

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

person Community    schedule 28.05.2009

При звонке:

foo( A(10) );

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

При звонке:

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

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

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

person ydebilloez    schedule 28.05.2009