对于以下程序:
#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);
}
GCC给出:
$ 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没有...)
答案 0 :(得分:3)
尽管有省略,但在我看来Visual Studio是错误的:
[C++17: class.inhctor.init]/1:
调用类型为B
的构造函数以初始化不同类型D
的对象时(即,构造函数被继承([namespace.udecl])时) ,初始化的过程就像使用默认的默认构造函数来初始化D
对象和继承该构造函数的每个基类子对象一样,不同之处在于B
子对象是通过调用来初始化的继承的构造函数。完整的初始化被认为是单个函数调用。特别是,在初始化D
对象的任何部分之前,先对继承的构造函数的参数进行初始化。
答案 1 :(得分:3)
看来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)
,所以这只是一个副本,而不是两个副本。