c ++ copy initialization&直接初始化,奇怪的情况

时间:2011-01-26 03:15:47

标签: c++ constructor copy-constructor explicit-constructor

在继续阅读本文之前,请先阅读Is there a difference in C++ between copy initialization and direct initialization?,确保您了解它的内容。

我将首先总结一下这条规则(阅读标准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中的规则,将考虑转换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)。然后转换后的临时prvalue将用于直接初始化返回的对象,这也应该没问题,因为直接初始化可以使用显式复制构造函数。所以GCC错了。

对于(2),

A a = b;

在我的理解中,首先通过运算符A()将b隐式转换为A,然后转换后的值将用于直接初始化a,当然可以调用显式复制构造函数?因此,这应该通过编译,所有编译器都错了?

注意,对于(2),visual c ++和clang都有类似的错误, “错误,无法从B转换为A”,但如果我在A的复制构造函数中删除了显式关键字,则错误消失了。

感谢阅读。


编辑1

因为有人还没有达到我的意思,我引用了8.5 / 16中的以下标准,

  

否则(即剩下的   复制初始化案例),   用户定义的转换序列   可以从源类型转换为   目的地类型或(当a   转换函数用于)   其派生类别列举   如13.3.1.4所述,最好的   一个是通过超载选择的   决议(13.3)。如果转换   无法做到或含糊不清的   初始化是不正确的。该   选择的函数用。调用   初始化表达式为   参数;如果函数是a   构造函数,调用初始化一个   临时的cv不合格   目标类型的版本。该   临时是一个prvalue。的结果   电话(这是暂时的)   然后使用构造函数case)   直接初始化,根据   以上规则,即对象   的目的地   副本初始化。在某些情况下,   允许实施   消除这种固有的复制   通过构造直接初始化   中间结果直接进入   正在初始化的对象;看到   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!
}

2 个答案:

答案 0 :(得分:8)

你声明了你的拷贝构造函数explicit(BTW,为什么?),这意味着它不能再用于隐式复制类对象了。为了使用此构造函数进行复制,您现在必须使用直接初始化语法。见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。虽然我不确定“提议的解决方案”应该是什么后果......

答案 1 :(得分: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;隐式地从A 2构造int,并且它已经习惯了 直接初始化时间返回值(R) 但是,应将直接初始化应用于第二次复制 从R到a
我找不到当前标准中明确指出的措辞 直接初始化应该应用两次 因为可以肯定这个序列破坏了explicit规范 感觉,即使编译器开发人员认为允许这一点,我也不会感到惊讶  是一个缺陷。

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