I am having trouble with multiple inheritance and the diamond problem.
The problem occurs because my base class constructor requires a parameter. The compiler tries to generate a default constructor for my two abstract classes, but that fails because the default constructor cannot determine the parameter for the base class.
I don't understand why my abstract classes are calling the base constructor. I thought the most derived class is the one that calls the virtual base class constructor.
Here is code that reproduces what I'm talking about:
class VirtualBase
{
public:
VirtualBase(int initial) :
count(initial)
{}
int getCount() const
{
return count;
}
void increment()
{
count++;
}
private:
int count;
};
class ContractA : public virtual VirtualBase
{
public:
virtual void doSomething() = 0;
};
class ContractB : public virtual VirtualBase
{
public:
virtual void doSomethingElse() = 0;
};
class Concrete : public ContractA, public ContractB
{
public:
Concrete() :
VirtualBase(0)
{}
virtual void doSomething()
{
increment();
}
virtual void doSomethingElse()
{
// etc...
}
};
int main()
{
Concrete concrete;
concrete.doSomething();
concrete.doSomethingElse();
return 0;
}
I get the following error (for each Contract):
main.cpp: In constructor ‘ContractA::ContractA()’:
main.cpp:29:7: error: no matching function for call to ‘VirtualBase::VirtualBase()’
class ContractA : public virtual VirtualBase
^
main.cpp:29:7: note: candidates are:
main.cpp:9:3: note: VirtualBase::VirtualBase(int)
VirtualBase(int initial) :
^
main.cpp:9:3: note: candidate expects 1 argument, 0 provided
main.cpp:4:7: note: VirtualBase::VirtualBase(const VirtualBase&)
class VirtualBase
^
main.cpp:4:7: note: candidate expects 1 argument, 0 provided
main.cpp: In constructor ‘Concrete::Concrete()’:
main.cpp:53:17: note: synthesized method ‘ContractA::ContractA()’ first required here
VirtualBase(0)
^
Your example compiles with EDG and clang but it does not compile with gcc. I'm not sure if the code should compile as is as it seems the constructor of the abstract base class is declared as deleted: According to 12.1 [class.ctor] paragraph 4, sixth bullet, the defaulted default constructor is declared as deleted if any subobject doesn't have a default constructor:
... A defaulted default constructor for class X is defined as deleted if: ...
- ...
- any potentially constructed subobject, except for a non-static data member with a brace-or-equalinitializer, has class type
M
(or array thereof) and eitherM
has no default constructor or overload resolution (13.3) as applied toM
’s default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor, or- ...
There is no special exemption for classes with virtual
bases with respect to creation of virtual
bases, i.e., the defaulted default constructor will be deleted.
For abstract classes it is apparently not necessary to call the virtual base from a constructors member initializer list. At least, that is what 12.6.2 [class.base.init] paragraph 8 says according to its note:
In a non-delegating constructor, if a given potentially constructed subobject is not designated by a meminitializer-id (including the case where there is no mem-initializer-list because the constructor has no ctorinitializer), then
- if the entity is a non-static data member that has a brace-or-equal-initializer and either
- the constructor’s class is a union (9.5), and no other variant member of that union is designated by a mem-initializer-id or
- the constructor’s class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id, the entity is initialized as specified in 8.5;
- otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
- otherwise, the entity is default-initialized (8.5).
[ Note: An abstract class (10.4) is never a most derived class, thus its constructors never initialize virtual base classes, therefore the corresponding mem-initializers may be omitted. — end note ] ...
The relevant part for the most derived base is in 12.6.2 paragraph 7, last sentence:
... A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
Unfortunately the C+03 language didn't make abstract classes a special case wrt. constructor invocations, and still as of C++11 and later the g++ compiler (my version 5.1.0) doesn't regard them as special for this.
And so in practice, for portable code, all bases must be initialized, as far as the compiler knows.
If you want to make it clear to a reader of the source code that these are dummy calls, then you can do that via comments, or better, expressed directly in the source code with an assert
:
#include <assert.h>
struct Dummy_call {};
class VirtualBase
{
private:
int count;
protected:
VirtualBase( Dummy_call ) { assert( false ); }
public:
auto getCount() const -> int {
return count;
}
void increment() {
++count;
}
VirtualBase( int const initial )
: count(initial)
{}
};
class ContractA
: public virtual VirtualBase
{
public:
virtual void doSomething() = 0;
ContractA(): VirtualBase( Dummy_call{} ) {}
};
class ContractB
: public virtual VirtualBase
{
public:
virtual void doSomethingElse() = 0;
ContractB(): VirtualBase( Dummy_call{} ) {}
};
class Concrete
: public ContractA
, public ContractB
{
public:
void doSomething() override {
increment();
}
void doSomethingElse() override {} // etc...
Concrete()
: VirtualBase{ 0 }
{}
};
int main()
{
Concrete concrete;
concrete.doSomething();
concrete.doSomethingElse();
}
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