复制构造函数需要临时对象

时间:2009-12-01 15:30:38

标签: c++ copy-constructor

以下代码仅在复制构造函数可用时有效。

当我添加print语句(通过std::cout)并使复制构造函数可用时,它不会被使用(我假设有这样的编译器技巧来删除不必要的副本)。

但是在下面的输出operator <<和函数plop()中(我创建一个临时对象)我不认为需要复制构造函数。当我通过const引用(或者我做错了)传递所有内容时,有人可以解释为什么语言需要它。

#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-apple-darwin10
  配置为:/ var / tmp / gcc / gcc-5646~6 / src / configure --disable-checking --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 --program-prefix = i686-apple-darwin10- --host = x86_64-apple-darwin10 --target = i686-苹果darwin10
  螺纹型号:posix
  gcc版本4.2.1(Apple Inc. build 5646)

     

[Alpha:〜/ X] myork%g ++ t.cpp
  t.cpp:在函数'int main()'中:
  t.cpp:10:错误:'N :: N(const N&amp;)'是私人的   t.cpp:25:错误:在此上下文中   t.cpp:10:错误:'N :: N(const N&amp;)'是私人的   t.cpp:26:错误:在此上下文中

这是一些真实代码的简化版本 在实际代码中,我有一个包含std :: auto_ptr的类。这意味着采用const引用的复制构造函数无效(没有一些工作),我收到一个错误,表明复制构造函数因为它而不可用:

也改变课程:

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

错误是:

  

t.cpp:25:错误:没有匹配函数来调用'N :: N(N)'

2 个答案:

答案 0 :(得分:15)

来自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
}
  

一开始可能会令人惊讶   视线,尤其是最流行的   编译器没有正确实现   这条规则(further details)。

这将通过Core Issue 391在C ++ 1x中修复。

答案 1 :(得分:5)

这里标准的适用部分是§8.5.3/ 5,它涵盖了引用的初始化和§3.10/ 6,它告诉了什么是rvalue和什么是左值(在C ++中并不总是很明显)。

在这种情况下,初始化表达式为:“N(1)”,因此您使用功能表示法显式创建对象。根据3.10 / 6,该表达式是一个右值。

然后我们必须按顺序遍历8.5.3 / 5中的规则,并使用第一个适用的规则。第一种可能性是表达式表示左值,或者可以隐式转换为左值。你的表达式是一个右值,并且隐式转换为左值将需要一个返回引用的转换函数,在这种情况下似乎不存在,因此似乎不适用。

下一条规则说引用必须是const T(这里就是这种情况)。在这种情况下,表达式是类类型的右值,并且与引用引用兼容(即引用是指同一个类,或类的基类)。这意味着第151页底部的子弹(C ++ 2003 PDF的179)似乎适用。在这种情况下,允许编译器将引用直接绑定到表示rvalue的对象,或者创建rvalue的临时副本,并绑定到该临时副本。

但是,无论哪种方式,标准明确要求:“无论副本是否实际完成,用于制作副本的构造函数都应该是可调用的。”

因此,我相信 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 &'

我的猜测是大多数其他编译器大致相同。由于它们优化了对复制构造函数的调用,因此它们通常不需要它存在或可访问。当你要求他们尽可能准确地符合标准时,他们会给出错误信息,因为即使他们不使用它,它在技术上也是必需的。