Следует ли копировать аргументы в унаследованные конструкторы при вызове базового ctor или нет?
Для следующей программы:
#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 не...)
2 ответа
Похоже, что 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, эквивалентность mem-инициализатора в p8. Следовательно, вы получите две копии Foo
: один в B
Синтезированный конструктор и один в A
настоящий конструктор.
Правила C++17, в результате P0136, очень разные ( N4659: [class.inhtor.init]): там мы напрямую вызываем A
конструктор. Мы не добавляем новый конструктор в B
больше - и это не механизм, который иначе выражается в языке. И потому что мы напрямую вызываем A(Foo)
это только одна копия вместо двух.
Несмотря на Elision, мне кажется, что Visual Studio не так:
[C++17: class.inhctor.init]/1:
Когда конструктор для типаB
вызывается для инициализации объекта другого типаD
(то есть, когда конструктор был унаследован ([namespace.udecl])), инициализация происходит так, как если бы для инициализации объекта использовался конструктор по умолчаниюD
объект и каждый подобъект базового класса, от которого унаследован конструктор, за исключением того, чтоB
подобъект инициализируется вызовом унаследованного конструктора. Полная инициализация считается одним вызовом функции; в частности, инициализация параметров унаследованного конструктора выполняется до инициализации любой частиD
объект.