6

对于以下程序:

#include <iostream>

struct Foo
{
    Foo() { std::cout << "Foo()\n"; }
    Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
    ~Foo() { std::cout << "~Foo()\n"; }
};

struct A
{
    A(Foo) {}
};

struct B : A
{
    using A::A;
};

int main()
{
    Foo f;
    B b(f);
}

海合会给出:

$ g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Foo()
Foo(const Foo&)
~Foo()
~Foo()

VS 2017(也在 C++17 模式下)提供:

Foo()
Foo(const Foo&)
Foo(const Foo&)
~Foo()
~Foo()
~Foo()

谁是对的,为什么?

(我们也不要忘记 VS 2017 没有正确执行强制复制省略。所以它可能只是副本是“真实的”,但 GCC 根据 C++17 规则省略了它,而 VS 没有……)

4

2 回答 2

6

Visual Studio 似乎还没有实现P0136。正确的 C++17 行为是单个副本,原始 C++14 行为是两个副本。


C++14 规则 ( N4140:[class.inhctor] ) 将解释:

struct B : A
{
    using A::A;
};

作为:

struct B : A
{
    B(Foo f) : A(f) { }
};

引入的构造函数在 p3 中指定,在 p8 中的 mem-initializer 等价。因此,您会得到两个副本Foo:一个到B' 的合成构造函数中,另一个到A' 的真实构造函数中。


作为 P0136 的结果,C++17 规则非常不同(N4659:[class.inhtor.init]):在那里,我们直接调用A的构造函数。这不像我们要添加一个新的构造函数B了——而且它不是一种可以用语言表达的机制。而且因为我们直接调用A(Foo),所以这只是一个副本而不是两个。

于 2019-05-21T15:18:08.423 回答
3

尽管有省略,但在我看来 Visual Studio 是错误的:

[C++17: class.inhctor.init]/1:B调用 type 的构造函数来初始化不同类型的对象时D(即,当构造函数被继承时 ([namespace.udecl])),初始化继续进行,就好像使用默认的默认构造函数来初始化D对象和每个基继承构造函数的类子对象,除了B子对象是通过调用继承的构造函数来初始化的。完整的初始化被认为是单个函数调用;特别是,继承的构造函数参数的初始化在D对象的任何部分的初始化之前进行排序。

于 2019-05-21T14:41:14.423 回答