15

Прямо сейчас, я изучаю функции Inheritance на C++ и хочу проверить недавно изученную концепцию виртуальных базовых классов. Я попробовал следующий простой код:Почему стандартная инициализация в C++ 11 ведет себя странно с виртуальными базовыми классами?

#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.

+3

Fyi, clang ++ 3.7 не показывает поведение, которое вы описываете. Обе версии выдают один и тот же результат (первый). – WhozCraig

+0

Вы вызываете конструктор копирования во втором случае. – lorro

+3

@UpAndAdam Самый производный класс отвечает за инициализацию виртуальных базовых классов, код не будет компилироваться, если OP не вызвал конструктор 'A' напрямую. И что это такое в * передаче в преобразованных строковых литералах, как будто они являются ints *? – Praetorian

ответ

8

Это ошибка компилятора в g ++.

В C++ 14 раздела (N4140) [dcl.init.list], определение списка инициализации является (под редакцией для краткости):

Список инициализации объекта или ссылки типа T определяются следующим образом:

  • Если T представляет собой агрегат, агрегат инициализация выполняются
  • в противном случае, если список инициализатора не имеет элементов и T является типом класса с конструктором по умолчанию , объект инициализируется значением.
  • В противном случае, если T является конкретизацией станда :: initializer_list, [...]
  • В противном случае, если T является типом класса, конструкторы рассматриваются. Соответствующие конструкторы перечислены, и лучший выбирается с помощью разрешения перегрузки. Если для преобразования любого из аргументов требуется сужение преобразования, программа плохо сформирована.
  • [...]

Первые 3 пункта не применяются: B не агрегат (агрегат не может иметь базовые классы), список инициализатора имеет элементы, B не конкретизацией от std::initializer_list.

Четвертый пункт не распространяется, потому что разрешение перегрузки соответствует B{p_value1, p_value2} конструктору B(int, int) согласно [over.match.list] /1.2:

Если нет жизнеспособного инициализатора-лист конструктора не найден, разрешение перегрузки выполняется снова, где функции-кандидаты - все конструкторы класса T, а список аргументов состоит из элементов списка инициализаторов.

Из последней цитаты, что B(whatever) и B{whatever} должны вести себя одинаково.

+0

Отличный ответ! Благодаря :) – SimplyOm