Неожиданное ссылочное поведение константы

#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

Мой вопрос: почему делает создание метода, который не называют частным изменением, компилирует ли этот код? Это передано под мандат стандартом? Существует ли обходное решение?

8
задан Zachary Vance 14 July 2010 в 18:17
поделиться

3 ответа

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

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

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

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

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

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

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

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

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

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

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

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

4
ответ дан 5 December 2019 в 20:13
поделиться

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

const A& b(B());

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

1
ответ дан 5 December 2019 в 20:13
поделиться

Итак, вы используете "копирование-инициализацию":

8.5/11 Инициализаторы

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

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

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 во временный объект. Ссылка привязывается к временному объекту или к подобъекту внутри временного объекта.

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

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

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

3
ответ дан 5 December 2019 в 20:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: