6

在继续阅读本文之前,请阅读C++ 中复制初始化和直接初始化有区别吗?首先,确保你理解它在说什么。

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

1) 对于直接初始化,所有的构造函数都将被视为重载集,重载解析将根据重载解析规则选择最佳的一个。

2) 对于复制初始化,源类型与目的类型相同或派生自目的类型,规则同上,只是只有转换构造函数(无显式构造函数)才会被视为重载集。这实际上意味着显式复制/移动构造函数不会被考虑到重载集中。

3)对于上面(2)中没有包括的复制初始化情况(源类型与目的类型不同,不是从目的类型派生的),我们首先考虑用户定义的可以从源类型转换为目的类型的转换序列或(当使用转换函数时)到其派生类。如果转换成功,则使用结果直接初始化目标对象。

3.1) 在此用户定义的转换序列期间,将根据 8.5/16 和 13.3.1.4 中的规则考虑转换 ctor(非显式 ctor)和非显式转换函数。

3.2)结果纯右值将直接初始化目标对象,如(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)。然后转换后的临时prvalue将用于直接初始化返回的对象,这也应该没问题,因为直接初始化可以使用显式复制构造函数。所以 GCC 是错误的。

对于 (2),

A a = b;

在我的理解中,首先b被操作符A()隐式转换为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!
}
4

2 回答 2

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
}

这就是阻止所有转换工作的原因,因为它们都依赖于复制初始化,而复制初始化又依赖于隐式复制。并且您禁用了隐式复制。

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

于 2011-01-26T03:38:12.073 回答
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在概念上将调用复制两次。从 2 隐式
return 2;构造一个,它用于直接初始化一个时间返回值 (R)。 但是,是否应该将直接初始化应用于从 R 到 的第二次复制? 我在当前标准中找不到明确指出直接初始化应该应用两次的措辞。 由于可以肯定这个序列在某种意义上破坏了规范,即使编译器开发人员认为允许这是一个缺陷,我也不感到惊讶。Aint
a

explicit

让我做一个不必要的补充。
编译器的不符合行为并不意味着编译器直接存在缺陷。
正如缺陷报告显示的那样,该标准已经存在缺陷。
例如,C 标准不允许从指向 T 类型数组的指针转换为指向 T 数组的指针const
这在 C++ 中是允许的,我认为在 C 中同样应该在语义上允许。
Gcc 会发出有关此转换的警告。Comeau 发出错误,并且无法编译。

于 2011-01-26T14:50:05.400 回答