为什么这里需要复制构造函数?

时间:2011-03-29 22:19:46

标签: c++ copy-constructor ternary-operator

请考虑以下代码:

struct S
{
    S() {}
    void f();
private:
    S(const S&);
};

int main()
{
    bool some_condition;
    S my_other_S;
    (some_condition ? S() : my_other_S).f();
    return 0;
}

gcc无法编译,说:

test.cpp: In function 'int main()':
test.cpp:6:5: error: 'S::S(const S&)' is private
test.cpp:13:29: error: within this context

我不明白为什么要在该行上进行复制构建 - 目的是简单地在默认构造的f()实例或S上调用my_other_S ,即它应该相当于:

if (some_condition)
    S().f();
else
    my_other_S.f();

第一种情况有什么不同,为什么需要复制构造函数?

编辑:那么,有没有办法在表达式上下文中表达“对预先存在的对象上的临时执行此操作”?

4 个答案:

答案 0 :(得分:9)

?:的结果是rvalue,一个新对象,如果其中一个参数是rvalue。要创建此rvalue,编译器必须复制结果。

if (some_condition)
    S().f(); // Compiler knows that it's rvalue
else
    my_other_S.f(); // Compiler knows that it's lvalue

这与你不能做的原因相同

struct B { private: B(const B&); };
struct C { C(B&); C(const B&); };
int main() {
    B b;
    C c(some_condition ? b : B());
}

我改变了我的榜样,因为旧的有点糟透了。你可以清楚地看到这里没有办法编译这个表达式,因为编译器无法知道要调用的构造函数。当然,在这种情况下,编译器可以将两个参数强制转换为const B&,但由于某些不太相关的原因,它不会。

编辑:不,没有,因为没有办法编译该表达式,因为关于它的重要数据(rvalue或lvalue)在运行时会有所不同。编译器尝试通过复制构造转换为rvalue来解决此问题,但它不能因为它无法复制而无法编译。

答案 1 :(得分:7)

来自[expr.cond](来自n3242草案的措辞):

  

否则,如果第二个和第三个操作数具有不同的类型并且具有(可能是cv限定的)类类型,或者两者都是相同值类别的glvalues和除cv-qualification之外的相同类型,则尝试将每个操作数转换为另一个操作数的类型。确定类型E1的操作数表达式T1是否可以转换为匹配类型E2的操作数表达式T2的过程定义如下:

     
      
  • 如果E2是左值,则E1可以转换为匹配E2,如果E1可以隐式转换(第4条)到类型“左值引用{ {1}}“,受限于在转换中,引用必须直接绑定(8.5.3)到左值。
  •   
  • 如果T2是xvalue:E2可以转换为匹配E1,如果E2可以隐式转换为“E1的右值引用” “,受限于引用必须直接绑定。
  •   
  • 如果T2是右值,或者上述两个转换都不能完成且至少有一个操作数具有(可能是cv-qualified)类类型:

         
        
    • 如果E2E1具有类类型,并且基础类类型相同或者一个是另一个的基类:E2可以转换为匹配{{1如果E1的类与E2的类相同,或者是T2的类的基类,则T1的cv-qualification与cv-qualification相同,或者比T2的cv资格更高的cv资格。如果应用了转换,则通过从T1复制初始化E1类型的临时值并将该临时值用作转换后的操作数,将T2更改为类型T2的prvalue
    •   
  •   

此规则提到了复制初始化,但它不适用,因为两个操作数具有相同的类型

  

如果第二个和第三个操作数是相同值类别的glvalues并且具有相同的类型,则结果   属于该类型和值类别,如果第二个或第三个操作数是位字段,则它是位字段,或者如果   两者都是比特字段。

此规则不适用,因为E1是左值而S()是左值。

  

否则,结果是prvalue。如果第二个和第三个操作数不具有相同的类型,并且具有(可能是cv限定的)类类型,则使用重载决策来确定要应用于操作数的转换(如果有)(13.3.1.2,13.6) 。如果重载决策失败,则程序格式错误。否则,应用如此确定的转换,并使用转换的操作数代替原始操作数   本节其余部分的操作数。   Lvalue-to-rvalue(4.1),array-to-pointer(4.2)和函数到指针(4.3)标准转换在第二个和第三个操作数上执行。在这些转换之后,以下之一应该成立:

     
      
  • 第二个和第三个操作数具有相同的类型;结果是那种类型。如果操作数具有类类型,结果是结果类型的prvalue临时值,它是从第二个操作数或第三个操作数复制初始化,具体取决于第一个操作数的值。
  •   

应用此规则,结果是复制初始化(强调我的)。

答案 2 :(得分:4)

这是一个古老的已知问题。见这里

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#446

根据委员会的决定,在您的示例中,?:运算符应始终返回临时对象,这意味着对于my_other_s分支,必须复制原始my_other_s对象。这就是编译器需要复制构造函数的原因。

这种语言还没有在C ++ 03中,但许多编译器从一开始就实现了这种方法。

答案 3 :(得分:1)

至于您更新的问题,如果修改S的定义是 允许,以下解决方案可能会有所帮助:

struct S
{
    ...
    S& ref() { return *this; } // additional member function
    ...
};

(some_condition ? S().ref() : my_other_S).f();

希望这有帮助