Почему единообразная инициализация в C++11 ведет себя странно с виртуальными базовыми классами?
Прямо сейчас я изучаю особенности наследования в C++ и хотел бы проверить недавно изученную концепцию виртуальных базовых классов. Я попробовал следующий простой код:
#include <iostream>
using namespace std;
class A
{
private:
int m_value;
string m_caller;
public:
A(int p_value, string p_caller) : m_value{p_value}, m_caller{p_caller}
{
cout<<"Instantiating A via "<<m_caller<<endl;
}
};
class B : virtual public A
{
private:
int m_value;
public:
B(int p_value1,int p_value2) : A{p_value1,"B"}, m_value{p_value2}
{
cout<<"Instantiating B."<<endl;
}
};
class C : public B
{
public:
C(int p_value1,int p_value2) : A{p_value1,"C"}, B(p_value1, p_value2)
{
cout<<"Instantiating C."<<endl;
}
};
int main()
{
C c1(1,2);
return 0;
}
Обратите внимание на B(p_value1, p_value2)
в конструкторе класса C. Это дало мне желаемый результат:
Instantiating A via C
Instantiating B.
Instantiating C.
Но в тот момент, когда я изменил его на B{p_value1, p_value2}
, я получил следующий результат:
Instantiating A via C
Instantiating A via B
Instantiating B.
Instantiating C.
Я попытался найти ответ, но все ответы, которые я получил, цитировали некоторые стандарты C++. Быть новичком в Упс, Я ищу более простое объяснение этому поведению.
Большое спасибо!
P. S. Я на C::B в Windows с компилятором G++ 4.8.1.
1 ответ:
Это ошибка компилятора в g++.
В разделе C++14 (N4140) [dcl.в этом.list], определение инициализации списка (отредактировано для краткости):
List-инициализация объекта или ссылки типа
T
определяется следующим образом:
- Если
T
является агрегатом, инициализация агрегата выполняется- в противном случае, если список инициализаторов не содержит элементов и
T
является типом класса с конструктором по умолчанию, объект значение инициализации.- в противном случае, если
T
является специализацией std::initializer_list, [...]- в противном случае, если
T
является типом класса, рассматриваются конструкторы. Соответствующие конструкторы перечисляются, и лучший из них выбирается с помощью разрешения перегрузки. Если для преобразования любого из аргументов требуется сужающее преобразование, программа плохо сформирована.- [...]
Первые 3 пункта не применяются:
B
не является агрегатом (aggregate не может иметь базовые классы), список инициализаторов содержит элементы,B
не является специализациейstd::initializer_list
.Четвертый пункт действительно применим, поскольку разрешение перегрузки соответствует
B{p_value1, p_value2}
конструкторуB(int, int)
в соответствии с [over.спичка.список] / 1.2:Из последней цитаты следует, чтоЕсли не найден жизнеспособный конструктор инициализатора-списка, разрешение перегрузки выполняется снова, где функции-кандидаты являются всеми конструкторами класса
T
, а список аргументов состоит из элементов списка инициализаторов.B(whatever)
иB{whatever}
должны вести себя одинаково.