Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do you need to call virtual base class constructor from all derived classes? Even if they're not the most derived?

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)
                 ^
like image 530
user2445507 Avatar asked Feb 06 '23 13:02

user2445507


2 Answers

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 either M has no default constructor or overload resolution (13.3) as applied to M’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.

like image 179
Dietmar Kühl Avatar answered Feb 09 '23 03:02

Dietmar Kühl


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();
}
like image 35
Cheers and hth. - Alf Avatar answered Feb 09 '23 02:02

Cheers and hth. - Alf