【问题标题】:Copy Constructor Needed with temp object临时对象需要复制构造函数
【发布时间】:2010-12-22 01:36:11
【问题描述】:

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

当我添加打印语句(通过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&)' 是私有的
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: 错误:没有匹配函数调用‘N::N(N)’

【问题讨论】:

  • 哪个编译器?这在 VC9 上编译得很好
  • @Captain:不是这样。两者都是有效的。我更喜欢上面使用的表格。
  • N const&amp; 完全有效。甚至有更好的理由。
  • 添加到 Naveen 的评论 - VC6、Digital Mars 和 Comeau 对您的示例也没有任何问题。只有 GCC 3.4.5(我没有安装较新的)抱怨复制 ctor。我不明白为什么应该这样做。
  • 这听起来和我最近遇到的一个问题非常相似:stackoverflow.com/questions/1615660/…

标签: c++ copy-constructor


【解决方案1】:

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

在这种情况下,您的初始化表达式是:“N(1)”,因此您正在使用函数符号显式创建一个对象。根据 3.10/6,该表达式是一个右值。

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

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

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

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

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

【讨论】:

  • 这里真的有副本'plop(N(1));'吗?如果有我看不到它,因为允许编译器将引用直接绑定到表示右值的对象。因此不需要对话的 OR 部分,因此不需要进行复制。不幸的是,下一段(迫使符合标准的编译器产生错误)伤害了我。
  • @Martin:我几乎无法想象一个编译器会真正复制,即使它的优化程度最低(当然它几乎不可能)。一线希望是大多数(即明智的)编译器通常不会强制执行它,并且在 C++ 0x 中,该要求将消失。
【解决方案2】:

来自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)。

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

【讨论】:

  • 我可以看到语句 'foo(makeA());' 需要一个复制构造函数因为它是将结果从函数 makeA() 复制回来的结果(即使编译器消除了实际副本,它也应该在那里)。但是声明 'foo(A())' 不应该需要副本(除了需要它的标准),我希望第 391 期旨在解决。
猜你喜欢
  • 2015-12-05
  • 2016-10-10
  • 1970-01-01
  • 2011-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-07
  • 1970-01-01
相关资源
最近更新 更多