2011-03-29 115 views
4

考虑下面的代码:为什么这里需要复制构造函数?

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(); 

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

EDIT:是否有任何方式,然后,在此表示“上执行此操作或者预先存在的对象上的临时”在表达式上下文?

+0

是不是if语句做你想要什么?在单个表达式中做所有事情的优点是什么(混淆除外)? – 2011-03-30 07:38:43

+0

@Bo if语句需要重复'f()'部分,这部分实际上可能是一个带有许多参数的函数,每个参数都可能是冗长的表达式的结果......您得到的想法 – HighCommander4 2011-03-30 19:37:34

+0

@Bo此外,在某些上下文可能无法将表达式重写为if语句 - 例如,如果该表达式在初始化列表中用作实例变量的初始值设定项。 – HighCommander4 2011-03-30 20:08:08

回答

8

?:结果是一个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&,但由于某种原因,这是不是很相关的,它不会。

编辑:没有,没有,因为没有办法编译的表达,因为它的重要数据(右值或左值)在运行时间变化。编译器试图通过转换通过复制来构建右值来解决这个问题给你,但不能因为它无法复制,因此它不能编译。

+0

@DeadMG:从我的阅读中,结果不会被复制。另一个表达呢。试图弄清楚这是否重要。 – 2011-03-29 22:28:20

+0

划伤我早先的陈述,我使用了错误的规则。 – 2011-03-29 22:34:55

+0

@ HighCommander4:它不起作用,因为编译器无法保证有关结果的事实。无论这个事实是否是这个类型,或者这个表达是右值还是左值,都是无关紧要的。 – Puppy 2011-03-29 22:42:02

7

[expr.cond](措词从n3242草案):

否则,如果所述第二和第三操作数具有不同的类型和或者具有(可能CV修饰)类型,或同一的,如果两者都是glvalues值类别和同型除了CV-资格,则尝试对每个那些操作数转换成其他的类型。用于确定操作数表达T1类型的E1是否可以转换,以匹配T2类型的操作数表达E2过程定义德音响如下:

  • 如果E2是一个左值:E1可以转换E2如果E1匹配可以隐式转换(第4)的类型“左值参照T2”,受约束的是,在转换的参考必须直接(8.5.3)绑定到左值。
  • 如果E2是一个x值:E1可以被转换以匹配E2如果E1可以隐式转换到输入“右值参照T2”,受该基准必须直接结合的约束。
  • 如果E2是一个rvalue或者如果既不以上的转化可以做到和操作数具有(可能CV修饰)类型中的至少一个:

    • 如果E1E2具有类类型,并且基础类的类型相同或者一个是另一个的基类:如果T2的类与T1的类或基类T1以及cv的类相同,则E1可以转换为匹配E2T2的资格与cv资格0相同或更高的cv资格。如果应用了转换,E1通过从E1复制初始化T2类型的临时文件并将该临时文件用作转换后的操作数而更改为类型T2的预值。

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

如果第二个和第三个操作数的值相同类别的glvalues和具有相同类型,结果 属于该类型和值类别,如果第二个或第三个操作数是位域,或者两者均为位域,则结果为位域。

此规则不适用,因为S()是一个右值,my_other_S是一个左值。

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

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

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

4

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

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

根据该委员会的决定,在你的榜样的?:运营商应该总是返回一个临时对象,这意味着对my_other_s分支,原my_other_s对象将被复制。这就是编译器需要拷贝构造函数的原因。

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

1

至于你更新的问题,如果S的定义的修改被允许 ,下面的工作,各地可能会有所帮助:

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

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

希望这有助于

+0

是的!这正是我对原始代码的意图。但不幸的是,我不得不编写和调用一个特殊的成员函数来实现它... – HighCommander4 2011-03-30 20:30:01

相关问题