2011-01-26 91 views
6

在继续阅读本文之前,请首先阅读Is there a difference in C++ between copy initialization and direct initialization?,确保你明白它在说什么。C++复制初始化和直接初始化,奇怪的情况

我会在这里先总结一下规则(读取标准n3225 8.5/16,13.3.1.3,13.3.1.4和13.3.1.5),

1)直接初始化,所有构造函数都会被视为重载设置,重载分辨率将根据重载解析规则选择最佳分辨率。

2)对于复制初始化和源类型与目标类型相同或从目标类型派生,除了仅将转换构造函数(构造函数没有显式)视为重载集外,其规则与上述相同。这实际上意味着显式复制/移动构造函数不会被视为重载集合。 3)对于上面(2)中未包括的复制初始化情况(源类型与目标类型不同并且不是源自目标类型),我们首先考虑用户定义的转换序列,其可以从源类型转换为目标类型或(当使用转换函数时)到其派生类。如果转换成功,则结果用于直接初始化的目标对象。在本用户定义的转换序列中,根据8.5/16和13.3.1.4中的规则,将考虑转换ctors(非显式ctors)和非显式转换函数。

3.2)结果prvalue将直接初始化为目标对象,如(1)中列出的规则,参见8.5/16。

好的,对规则来说足够了,让我们看看一些奇怪的代码,我真的不知道我的推理是错误的,还是只是所有的编译器都是错误的。请帮助我,谢谢。

struct A 
{ 
    A (int) { } 
    A() { } 
    explicit A(const A&) { } 
}; 
struct B 
{ 
    operator A() { return 2; } 
    //1) visual c++ and clang passes this 
    //gcc 4.4.3 denies this, says no viable constructor available 
}; 
int main() 
{ 
    B b; 
    A a = b; 
    //2) oops, all compilers deny this 
} 

在我的理解,对于(1),

operator A() { return 2; } 

因为C++具有函数返回被作为复制初始化,根据上述规则的规则,2将被首先隐式转换到A,这应该是确定的,因为A有一个构造函数A(int)。然后,转换后的临时值将被用来直接初始化返回的对象,这应该也可以,因为直接初始化可以使用显式拷贝构造函数。所以GCC是错误的。

对于(2),

A a = b; 

在我的理解,首先b为隐式转换为A,由运营商A(),然后将转换后的值将用于直接初始化,这可当然要调用显式拷贝构造函数?因此,这应该通过编译和所有编译器都是错误的?请注意,对于(2),visual C++和clang都有类似于 的错误“错误,无法从B转换为A”,但如果我在A的复制构造函数中删除显式关键字,则错误不见了..

感谢您的阅读。


编辑1

因为有人还是没有得到我的意思,我引述8.5/16以下的标准,

否则(即,对剩余的 复印通初始化情况), 用户定义的转换序列 可以从源类型转换为 目标类型或(当使用 转换功能时)转换为 的派生类如13.3.1.4中所述列举为 ,并且最好的 通过分解(13.3)的过载 来选择。如果转换 无法完成或不明确,则 初始化格式不正确。使用 初始化程序表达式作为其参数 调用所选的 函数;如果该函数是构造函数 ,则该调用将初始化 暂时的目标类型的cv未合格版本 版本。 临时是一个prvalue。的 呼叫的结果(这是临时用于 构造情况下)随后被用于 直接初始化,根据上述规则 ,那是 目的地的 复制初始化的对象。在某些情况下, 的执行被允许为 通过构造 直接初始化对象的 直接初始化来消除此 直接初始化中固有的复制;见 12.2,12.8。

请注意,它确实提到在用户定义的转换后直接初始化。这意味着,根据我的理解,以下代码应遵守规则,正如我所评论的,这是由clang,coomeau online,visual C++所证实的,但GCC 4.4.3不能同时满足(1)和(2)的要求。虽然这是一个奇怪的规则,但它遵循标准的推理。

struct A 
{ 
    A (int) { } 
    A() { } 
    explicit A(const A&) { } 
}; 

int main() 
{ 
    A a = 2; //1)OK, first convert, then direct-initialize 
    A a = (A)2; //2)oops, constructor explicit, not viable here! 
} 
+0

Comeau Online以书面形式接受整个代码片段。 –

+0

好的,谢谢。这给了我一些信心,我的推理至少不会走向错误的方向。^_^ – user534498

+0

@詹姆斯麦克奈利斯:然而,科莫在线拒绝我的答案中的代码,尽管根据OP的推理它应该被接受。 – AnT

回答

8

你宣布你的拷贝构造函数explicit(顺便说一句,为什么?),这意味着它不能再用于类对象的隐式拷贝。为了使用这个构造函数进行复制,你现在被迫使用直接初始化语法。请参阅12.3.1/2

2显式构造函数与非显式构造函数一样构造对象,但只在直接初始化语法(8.5)或转换(5.2.9,5.4)用过的。

这个问题可以通过下面的更短的例子

struct A { 
    A() {} 
    explicit A(const A&) {} 
}; 

int main() { 
    A a; 
    A b = a; // ERROR: copy-initialization 
    A c(a); // OK: direct-initialization 
} 

这是您的转换块都来自工作,因为它们都依赖于复制初始化,这又依赖于来说明隐式复制。并且你禁用隐式复制。

此外,请参阅Defect Report #152其中涵盖此特定问题。虽然我不确定“提议的决议”的后果应该是什么......

+0

您好,您没有阅读标准8.5/16中的规则。在复制初始化期间,直接初始化将在用户定义的转换后使用,当然这将允许使用显式复制构造函数。 – user534498

+0

@ user534498:好的,12.3.1/2似乎需要直接初始化*语法*以使显式构造函数可用。仅仅是直接初始化是不够的,*语法*必须存在于代码中。它不存在于你的。 – AnT

+0

嗨安德烈,请阅读我的编辑1.顺便说一句,我认为在12.3.1/2中的例子并不矛盾我的上述推理,因为它不涉及显式拷贝构造函数。 – user534498

0

正如你所提到的,gcc不会编译下面的代码。

struct A { 
    A(int) {} 
    explicit A(A const&) {} 
}; 

int main() { 
    A a = 2; 
} 

我认为这是不符合标准符合现行标准8.5 p15。
但是,对于你的第一个案例,

struct A { 
    A(int) {} 
    explicit A(A const&) {} 
}; 

struct B { 
    operator A() { return 2; } 
}; 

int main() { 
    B b; 
    A a = b; 
} 

我不相信,允许这是符合的。
如您所知,return将在概念上调用两次复制。
return 2;隐含地从int 2构建A,并且它被用于 直接初始化时间返回值(R)。
但是,是否应该将直接初始化应用于从R到a的第二个复制 ?
我无法找到当前标准中明确指出 直接初始化应该应用两次的措辞。
由于确定这个序列破坏了explicit规范的意义,即使编译器开发人员认为允许这个 是一个缺陷,我也不会感到惊讶。

让我做一个不必要的补充。
编译器的不一致行为并不意味着编译器直接具有 缺陷。
该标准已经有缺陷报告显示的缺陷。
例如,C标准不允许从一个指针转换为 类型T的阵列,一个指针到的const数组T.
这是允许在C++中,我认为它应该被允许在语义上类似于C 。
Gcc发出有关此转换的警告。Comeau发出错误,并且不会编译。