Необходим конструктор копирования с временным объектом
Следующий код работает только тогда, когда доступен конструктор копирования.
Когда я добавляю печатные заявления (через std::cout
) и сделать конструктор копирования доступным, он не используется (я предполагаю, что так происходит трюк с компилятором для удаления ненужной копии).
Но в обоих выходных operator <<
и функция plop()
ниже (где я создаю временный объект) я не вижу необходимости в конструкторе копирования. Может кто-нибудь объяснить, почему язык нуждается в этом, когда я передаю все по константной ссылке (или что я делаю неправильно).
#include <iostream>
class N
{
public:
N(int) {}
private:
N(N const&);
};
std::ostream& operator<<(std::ostream& str,N const& data)
{
return str << "N\n";
}
void plop(std::ostream& str,N const& data)
{
str << "N\n";
}
int main()
{
std::cout << N(1); // Needs copy constructor (line 25)
plop(std::cout,N(1)); // Needs copy constructor
N a(5);
std::cout << a;
plop(std::cout,a);
}
Составитель:
[Alpha: ~ / X] myork% g ++ -v
Используя встроенные спецификации.
Цель: i686-яблоко-дарвин10
Настраивается с помощью: /var/tmp/gcc/gcc-5646~6/src/configure --disable-проверяя --enable-werror --prefix=/usr --mandir=/share/man --enable-languages =c,objc, C++,obj-C++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --with-gxx-include-dir=/include/ C++/4.2.1 - префикс программы =i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686- Яблочно-darwin10
Модель потока: posix
gcc версия 4.2.1 (Apple Inc., сборка 5646)[Alpha: ~ / X] myork% g ++ t.cpp
t.cpp: в функции 'int main()':
t.cpp:10: ошибка: 'N::N(const N&)' является приватным
t.cpp:25: ошибка: в этом контексте
t.cpp:10: ошибка: 'N::N(const N&)' является приватным
t.cpp:26: ошибка: в этом контексте
Это упрощенная версия реального кода.
В реальном коде у меня есть класс, который содержит std::auto_ptr. Это означает, что конструктор копирования, который принимает константную ссылку, недопустим (без некоторой работы), и я получил ошибку, указывающую, что конструктор копирования не был доступен из-за этого:
Измените класс тоже:
class N
{
public:
N(int) {}
private:
std::auto_ptr<int> data;
};
Ошибка тогда:
t.cpp:25: ошибка: нет соответствующей функции для вызова 'N::N(N)'
2 ответа
С http://gcc.gnu.org/gcc-3.4/changes.html
При связывании rvalue типа класса со ссылкой, конструктор копирования класса должен быть доступен. Например, рассмотрим следующий код:
class A
{
public:
A();
private:
A(const A&); // private copy ctor
};
A makeA(void);
void foo(const A&);
void bar(void)
{
foo(A()); // error, copy ctor is not accessible
foo(makeA()); // error, copy ctor is not accessible
A a1;
foo(a1); // OK, a1 is a lvalue
}
На первый взгляд это может удивить, тем более что большинство популярных компиляторов неправильно реализуют это правило ( более подробно).
Это будет исправлено в C++1x основной проблемой 391.
Применимыми частями стандарта здесь являются §8.5.3/5, который охватывает инициализацию ссылок, и §3.10/6, в котором говорится, что такое r-значение, а что - l-значение (не всегда очевидно в C++).
В этом случае ваше выражение инициализации: "N(1)", поэтому вы явно создаете объект с использованием функциональной нотации. Согласно 3.10 / 6, это выражение является rvalue.
Затем мы должны пройти по правилам в 8.5.3 / 5 по порядку и использовать первое, что применимо. Первая возможность, если выражение представляет lvalue или может быть неявно преобразовано в lvalue. Ваше выражение является rvalue, и для неявного преобразования в lvalue потребуется функция преобразования, которая возвращает ссылку, которой в данном случае не существует, так что, похоже, она не применяется.
Следующее правило гласит, что ссылка должна быть на const T (что здесь имеет место). В этом случае выражение является значением типа класса и совместимо со ссылкой со ссылкой (т. Е. Ссылка относится к тому же классу или базе класса). Это означает, что маркер внизу страницы 151 (179 в C++ 2003 PDF), похоже, подходит. В этом случае компилятору разрешается либо привязывать ссылку непосредственно к объекту, представляющему значение r, либо ИЛИ создавать временную копию значения r и связывать эту временную копию.
В любом случае, однако, стандарт явно требует, чтобы: "Конструктор, который будет использоваться для создания копии, должен вызываться независимо от того, выполняется ли копия на самом деле".
Таким образом, я считаю, что gcc прав, когда выдает сообщение об ошибке, а другие технически ошибочны, чтобы принять код. Я немного упростил ваш код до следующего:
class N {
public:
N(int) {}
private:
N(N const&);
};
void plop(N const& data) { }
int main() {
plop(N(1));
}
Когда вызывается с "--A" (режим строгих ошибок), Comeau выдает следующее сообщение об ошибке:
"plop.cpp", line 12: error: "N::N(const N &)", required for copy that was
eliminated, is inaccessible
plop(N(1));
^
Аналогично, когда вызывается с "/Za" (его режим "соответствия ANSI"), VC++ 9 дает:
plop.cpp
plop.cpp(12) : error C2248: 'N::N' : cannot access private member declared in class 'N'
plop.cpp(6) : see declaration of 'N::N'
plop.cpp(2) : see declaration of 'N'
while checking that elided copy-constructor 'N::N(const N &)' is callable
plop.cpp(6) : see declaration of 'N::N'
when converting from 'N' to 'const N &'
Я предполагаю, что большинство других компиляторов делают примерно то же самое. Поскольку они оптимизируют вызов конструктора копирования, они обычно не требуют, чтобы он существовал или был доступен. Когда вы просите их максимально точно соответствовать стандарту, они выдают сообщение об ошибке, поскольку это технически необходимо, даже если они его не используют.