#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
Мой вопрос: почему делает создание метода, который не называют частным изменением, компилирует ли этот код? Это передано под мандат стандартом? Существует ли обходное решение?
Важное словоблудие в текущем стандарте (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 в в первом случае и в объект, который является результатом преобразования во втором случае (или, в любом случае, в соответствующий подобъект базового класса объекта).
Применяется первый случай, и ссылка «напрямую связана» с выражением инициализатора.
Я думаю, что это действительно ошибка компилятора, gcc, кажется, думает, что это инициализация копии. Вместо этого используйте прямую инициализацию:
const A& b(B());
Вызов конструктора копирования при инициализации копии всегда оптимизирован (экземпляр исключения копирования), и тогда он не обязательно должен быть доступен.
Итак, вы используете "копирование-инициализацию":
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 (комментарии приветствуются). Мы определенно достигаем пределов моих лингвистических навыков...