8

这是说明问题的最小代码示例:

#include <iostream>

class Thing
{
   // Non-copyable
   Thing(const Thing&);
   Thing& operator=(const Thing&);

   int n_;

public:
   Thing(int n) : n_(n) {}

   int getValue() const { return n_;}
};

void show(const Thing& t)
{
   std::cout << t.getValue() << std::endl;
}

int main()
{
   show(3);
}

这会产生相同的错误:

int main()
{
    show( Thing(3) );
}

AIX 下的 IBM XL C/C++ 8.0 编译器发出以下警告:

"testWarning.cpp", line 24.9: 1540-0306 (W) The "private" copy constructor "Thing(const Thing &)" cannot be accessed.
"testWarning.cpp", line 24.9: 1540-0308 (I) The semantics specify that a temporary object must be constructed.
"testWarning.cpp", line 24.9: 1540-0309 (I) The temporary is not constructed, but the copy constructor must be accessible.

我还使用“-Wall”和“-pedantic”尝试了 g++ 4.1.2,但没有得到诊断。为什么这里需要访问复制构造函数?除了使对象可复制(不在我的控制范围内)或使显式副本通过(当现实生活中的对象复制成本很高时)之外,我如何消除警告?

4

4 回答 4

9

这方面的规则在标准的 §8.5.3/5 中。确定了三种基本情况。第一个涉及初始化程序(在您的情况下为“3”)是左值或具有类类型。由于这些都不正确,因此您所拥有的是第三种情况:使用没有类类型的右值初始化 const 引用。8.5.3/5 中的最后一个项目符号涵盖了这种情况:

否则,使用非引用复制初始化规则(8.5)从初始化表达式创建并初始化“cv1 T1”类型的临时变量。然后将引用绑定到临时文件。如果 T1 与 T2 引用相关,则 cv1 必须与 cv2 具有相同的 cv 限定或大于 cv2 的 cv 限定;否则,程序格式错误。

编辑:重读,我认为 IBM 是对的。我以前在考虑必须复制临时文件的可能性,但这不是问题的根源。要使用第 8.5 节中指定的非引用复制初始化创建临时,它需要复制 ctor。特别是,在这一点上,它等价于如下表达式:

T x = a;

这基本上相当于:

T x = T(a);

即需要创建一个临时对象,然后将临时对象复制到正在初始化的对象(在这种情况下,它也是一个临时对象)。总结一下所需的过程,大致相当于如下代码:

T temp1(3);
T temp2(temp1); // requires copy ctor
show(temp2);    // show's reference parameter binds directly to temp2
于 2009-10-23T20:50:44.883 回答
3

C++ 允许足够智能的编译器避免复制临时对象,这是对标准允许的as-if规则的一种违反。我不熟悉 IBM 的 AIX C++ 编译器,但听起来它认为show(3)调用需要复制临时的事物。在这种情况下,C++ 要求您有一个可访问的复制构造函数,即使您的编译器足够聪明,可以避免使用它。

但为什么show(3)首先需要副本?我想不通。运气好的话,litb 很快就会出现。

于 2009-10-23T20:26:02.483 回答
1

我的直觉是杰瑞的答案是正确的,但仍有一些问题。

有趣的是,该节的前一段有一个核心问题(391)。该问题与参数是同一类类型时有关。具体来说:

int main () {
  show ( Thing (3) );       // not allowed under current wording
                            // but allowed with Core Issue 391

  show ( 3 );               // Still illegal with 391
}

核心问题391中的更改仅影响右值临时具有相同类类型的位置。之前的措辞是:

如果初始化表达式是右值,T2 是类类型,并且与cv1 T1引用兼容,cv2 T2,则绑定如下:

[...]

无论复制是否实际完成,用于制作复制的构造函数都应该是可调用的。

show(Thing(3))根据当前标准,最后一行是非法的。本节的建议措辞是:

如果初始化表达式是右值,T2 是类类型,并且“cv1 T1”与“cv2 T2”引用兼容,则引用绑定到右值表示的对象(参见 3.10 [basic.lval])或到该对象内的子对象。

在这一点上,我认为 g++ 可能已经按照391更新了它的行为,但是这个更改意外地包含了复制初始化的情况。但是,我测试的 g++ 版本并未证明这一点:

class A{
public:
  A ();
  A (int);
private:
  A (A const &);
};

void foo (A const &);

void foo ()
{
  A a = 3 ;     // 3.2.3 (ERROR), 3.4.6(ERROR), 4.4.0(ERROR), Comeau(ERROR)
  
  foo ( 3 ) ;   // 3.2.3 (OK), 3.4.6(OK), 4.4.0(OK), Comeau(OK)
  foo ( A() );  // 3.2.3 (OK), 3.4.6(ERROR), 4.4.0(OK), Comeau(OK)
  foo ( A(3) ); // 3.2.3 (OK), 3.4.6(ERROR), 4.4.0(OK), Comeau(OK)
}

我无法在 Jerry 对foo (3)此案的解释中找到错误,但是,由于不同编译器行为之间的差异,我确实有疑问。

于 2009-10-26T13:15:36.837 回答
0

如果您尝试命名临时事物会发生什么?

Thing temp(3);
show(temp);

于 2009-10-23T20:41:41.970 回答