Неожиданное эталонное поведение - PullRequest
8 голосов
/ 14 июля 2010
#include <iostream>

class A { 
  public:  
    A(){ cerr << "A Constructor" << endl; }  
    ~A(){ cerr << "A Destructor" << endl; }  
    A(const A &o){ cerr << "A Copy" << endl; } 
    A& operator=(const A &o){ cerr << "A Assignment" << endl; return *this; }
};


class B : public A { 
  public:  
    B() : A() { cerr << "B Constructor" << endl; }  
    ~B(){ cerr << "B Destructor" << endl; }
  private:
    B(const B &o) : A() { cerr << "B Copy" << endl; } 
    B& operator=(const B &o){ cerr << "B Assignment" << endl; return *this; }
};

int main() {  
  A a;  
  const A &b = B();  
  return 0; 
}

В GCC 4.2 я получаю это сообщение:

In function 'int main()':
Line 16: error: 'B::B(const B&)' is private
compilation terminated due to -Wfatal-errors.

Если я удаляю "частное" из B, я получаю ожидаемый вывод:

A Constructor
A Constructor
B Constructor
B Destructor
A Destructor
A Destructor

Мой вопрос: почему создание метода, который не называется private, изменяется, компилируется ли этот код?Является ли это стандартным?Есть ли обходной путь?

Ответы [ 3 ]

4 голосов
/ 14 июля 2010

Важное словосочетание в текущем стандарте (C ++ 03), по-видимому, содержится в §8.5.3, который объясняет, как инициализируются ссылки (в этих кавычках T1 - это тип инициализируемой ссылки, а T2 - это тип выражения инициализатора).

Если выражение инициализатора является rvalue, с T2 типом класса, а "cv1 T1" совместимо со ссылками с "cv2 T2, "ссылка связана одним из следующих способов (выбор определяется реализацией):

- ссылка связана с объектом, представленным значением r (см. 3.10), или подобъектомвнутри этого объекта.

- Создается временный объект типа "cv1 T2" [sic], и вызывается конструктор для копирования всего объекта rvalue во временный объект.Ссылка привязана к временному объекту или к подобъекту во временном объекте.

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

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

Обратите внимание, что это изменяется в C ++ 0x в соответствии с разрешением CWG дефект 391 .Новый язык читает (N3092 §8.5.3):

В противном случае, если T2 является типом класса и

- выражение инициализатора является rvalue и "cv1 T1 "совместим со ссылками с" cv2 T2, "

- T1 не связан со ссылками с T2, и выражение инициализатора может быть неявно преобразовано в значение типа" cv3 T3"(это преобразование выбирается путем перечисления применимых функций преобразования (13.3.1.6) и выбора наилучшего с помощью разрешения перегрузки (13.3)),

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

Первый случай применяется, и ссылка "привязаны напрямую к выражению инициализатора.

3 голосов
/ 14 июля 2010

Итак, вы используете 'copy-initialization':

8.5 / 11 Initializers

Форма инициализации (с использованием скобок или =), как правило, незначительна, ноимеет значение, когда инициализируемая сущность имеет тип класса;увидеть ниже....

Инициализация, которая происходит при передаче аргумента, возврате функции, генерации исключения (15.1), обработке исключения (15.3) и заключенных в скобки списках инициализаторов (8.5.1), называется copy-initializationи эквивалентно форме

T x = a;

Инициализация, которая происходит в новых выражениях (5.3.4), выражениях static_cast (5.2.9), преобразованиях типов функциональных обозначений (5.2.3), а также в основе и членеИнициализаторы (12.6.2) называются прямой инициализацией и эквивалентны форме

T x(a);

В 13.3.1.3 «Инициализация с помощью конструктора» перегрузки для выбранного конструктора:

Когда объекты типа класса инициализируются напрямую (8.5) или инициализируются копией из выражения того же или производного типа класса (8.5), разрешение перегрузки выбирает конструктор.Для прямой инициализации все функции-кандидаты являются конструкторами класса инициализируемого объекта.Для инициализации копирования все функции-кандидаты являются конструкторами преобразования (12.3.1) этого класса.

Таким образом, для инициализации копирования должен быть доступен конструктор копирования.Однако компилятору разрешено «оптимизировать» копию:

12.2 / 1 Временные объекты

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

Вы можете получить желаемый эффект, избегая инициализации копирования и используя прямую инициализацию:

 const A &b(B());  

Примечание:

С более новымиверсии GCC, по-видимому, имеют другое поведение, я думал, что я опубликую эту заметку, которая может устранить разницу (при том, что оба поведения по-прежнему соответствуют стандартам):

8.5.3 / 5 Ссылки говорят:

Если выражение инициализатора является r-значением, с T2 типом класса, а «cv1 T1» совместимо со ссылками с «cv2 T2», ссылка связывается одним из следующих способов (выбор - реализацияопределены):

  • Ссылка привязана к объекту, представленному значением r (см. 3.10), или подобъекту в этом объекте.

  • Создается временный объект типа «cv1 T2» [sic], и вызывается конструктор для копирования всего объекта rvalue во временный объект.Ссылка привязана к временному или подобъекту во временном.

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

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

Я не уверен, что именно это происходит между различными версиями GCC (комментарии приветствуются).Мы определенно достигли предела моих языковых навыков юриста ...

1 голос
/ 14 июля 2010

Я думаю, что это действительно ошибка компилятора, gcc, кажется, думает, что это инициализация копии. Вместо этого используйте прямую инициализацию:

const A& b(B());

Вызов конструктора копирования при инициализации копирования всегда оптимизируется (экземпляр разрешения копирования), а затем не должен быть доступен.

...