Необходим конструктор копирования с временным объектом - PullRequest
11 голосов
/ 01 декабря 2009

Следующий код работает только тогда, когда доступен конструктор копирования.

Когда я добавляю операторы печати (через 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. Это означает, что конструктор копирования, который принимает ссылку на const, недопустим (без какой-либо работы), и я получил ошибку, указывающую, что конструктор копирования не был доступен из-за этого:

Измените класс тоже:

class N
{
    public:
        N(int)  {}
    private:
        std::auto_ptr<int>  data;
};

Ошибка при этом:

t.cpp: 25: ошибка: нет соответствующей функции для вызова for N :: N (N) ’

Ответы [ 2 ]

15 голосов
/ 01 декабря 2009

С http://gcc.gnu.org/gcc-3.4/changes.html

При связывании значения класса на ссылку, конструктор копирования класса должны быть доступны. За Например, рассмотрим следующий код:

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 .

5 голосов
/ 01 декабря 2009

Применимые части стандарта здесь - §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 &'

Я предполагаю, что большинство других компиляторов делают примерно то же самое. Поскольку они оптимизируют вызов конструктора копирования, они обычно не требуют, чтобы он существовал или был доступен. Когда вы просите их максимально точно соответствовать стандарту, они выдают сообщение об ошибке, поскольку это технически необходимо, даже если они его не используют.

...