Right now, I am learning the features of Inheritance in C++ and wanted to test out the recently learnt concept of Virtual Base classes. I tried the following simple code:
#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;
}
Please note the B(p_value1, p_value2)
in the constructor of class C. This gave me the desired output:
Instantiating A via C
Instantiating B.
Instantiating C.
But, the moment I changed it to B{p_value1, p_value2}
, I got the following output:
Instantiating A via C
Instantiating A via B
Instantiating B.
Instantiating C.
I tried looking for the answer, but all the answers I got quoted some C++ standards. Being a beginner in OOPs, I am looking for a simpler explanation for this behaviour. Thanks a lot!
P.S. I am using C::B in Windows with compiler g++ 4.8.1.
Virtual base classes are used in virtual inheritance in a way of preventing multiple “instances” of a given class appearing in an inheritance hierarchy when using multiple inheritances. Need for Virtual Base Classes: Consider the situation where we have one class A .
Virtual base class in C++ Virtual classes are primarily used during multiple inheritance. To avoid, multiple instances of the same class being taken to the same class which later causes ambiguity, virtual classes are used.
This is a compiler bug in g++.
In C++14 (N4140) section [dcl.init.list], the definition of list initialization is (edited for conciseness):
List-initialization of an object or reference of type
T
is defined as follows:
- If
T
is an aggregate, aggregate initialization is performed- Otherwise, if the initializer list has no elements and
T
is a class type with a default constructor, the object is value-initialized.- Otherwise, if
T
is a specialization of std::initializer_list, [...]- Otherwise, if
T
is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution. If a narrowing conversion is required to convert any of the arguments, the program is ill-formed.- [...]
The first 3 points don't apply: B
is not an aggregate (aggregate cannot have base classes), the initializer list does have elements, B
is not a specialization of std::initializer_list
.
The fourth point does apply because overload resolution matches B{p_value1, p_value2}
to the constructor B(int, int)
according to [over.match.list]/1.2:
If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class
T
and the argument list consists of the elements of the initializer list.
It follows from the last quote that B(whatever)
and B{whatever}
should behave identically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With