2

I want to create a class that is a subclass of two classes that have a common virtual base class. Furthermore, I want this class to be abstract (not be able to create instances of it, but also not needing to call the ctor of the virtual base class).

Sample code:

#include <cstdio>
class CommonBaseClass {
public:
    virtual void DoSomething() = 0;
    virtual void DoSomethingElse() = 0;

    CommonBaseClass(int a) {}
};

class ClassA : public virtual CommonBaseClass {
public:
    virtual void DoSomething() override {
        printf("SOMETHING\n");
    }
    ClassA() {};
};

class ClassB : public virtual CommonBaseClass {
public:
    virtual void DoSomethingElse() override {
        printf("SOMETHING ELSE\n");
    }
    ClassB() {};
};

class TargetClass : //I will never instantiate this class
    public ClassA,
    public ClassB
{
public:
    TargetClass() : ClassA(), ClassB() {} //I don't want to call the CommonBaseClass ctor
};

class SubclassOfTarget :
    public TargetClass
{
public:
    SubclassOfTarget(int a) : CommonBaseClass(a), TargetClass()
    {}
};

int main()
{
    SubclassOfTarget c(15);
    c.DoSomething();
    c.DoSomethingElse();
}

If you try to compile this program, you will see that the compiler complains that TargetClass does not call the ctor of CommonBaseClass.

Promitheas Nikou
  • 511
  • 4
  • 14
  • In a class with no bases, marking a function `virtual` and `final` seems contradictory. – Pete Becker Jun 25 '22 at 21:22
  • First of all, it is a minimal reproducible example, that's all the code you need, the methods aren't related to the current problem. Secondly, no it should not. If a class is abstract, you need not call the constructor of the virtual base class, only of the direct non-virtual superclasses. – Promitheas Nikou Jun 25 '22 at 21:25
  • There are a lot of class declarations, If I were to put all of them here, the question would be about 300 lines. I can add a few more if you want, but I'm telling you, they won't give you any new information. – Promitheas Nikou Jun 25 '22 at 21:26
  • You can declare a method pure virtual and still provide an implementation for it. If you don't have any method handy to declare pure virtual, you can always do this with the destructor. – Igor Tandetnik Jun 25 '22 at 21:29
  • 1
    No, it is not a [mre]. Can you answer the following question: can everyone else cut/paste what's shown in your question, into a new empty file, then attempt to compile and get the same compilation error that the "compiler complains about it"? Unless your answer is "yes" (and it's obviously "no", since a bunch of relevant code is simply not shown), how can you claim that it's a [mre]? Noone can reproduce it. – Sam Varshavchik Jun 25 '22 at 21:29
  • Actually you can. (Just create a dummy declaration for `Item` and `ALLEGRO_BITMAP`, unless you want to download the ALLEGRO library and setup a project to use it). When you try to compile it, it should produce a similar error. BTW, did you actually try compiling it, or just complained without even trying? – Promitheas Nikou Jun 25 '22 at 21:32
  • 1
    The problem is that if you ask ten C++ developers to create a "dummy declaration" for something, you'll get at least eleven completely different examples, in response. There's obviously, somewhere, code that results in an attempt to instantiate a class that cannot be instantiated. Staring at the declaration of all the classes in question isn't sufficient to telepathically deduce what that instantiation looks like, why it happens, and explain the reasons for that. It is unlikely that without a [mre] anyone will be able to explain anything here. – Sam Varshavchik Jun 25 '22 at 21:35
  • By dummy declaration I mean empty declaration(definition actually but whatever): `void func(){}` `class SomeClass {};` or even `class Someclass;` – Promitheas Nikou Jun 25 '22 at 21:35
  • 1
    Try to compile what, exactly? Cut/pasting the individual code chunks doesn't have a prayer of compiling without resulting in a metric ton of completely irrelevant compilation errors. The best way of getting help on Stackoverflow is to make it as easy as possible for everyone to reproduce your own problem, with as little work as possible. And adding this specific dummy declaration, to all of the rest of the shown code, is not going to be helpful. Can you please [edit] your question, and include ***everything*** that's needed to reproduce your problematic error, and ***only*** that. – Sam Varshavchik Jun 25 '22 at 21:37
  • 1
    @PromitheasNikou — “by dummy declaration I mean …” — so add it to your code and make sure that the modified code shows the problem. – Pete Becker Jun 25 '22 at 21:40
  • I have put the headers, not the source files, do you want me to paste the source files too? – Promitheas Nikou Jun 25 '22 at 21:41
  • @PromitheasNikou please read what a [mre] is. The code you show here should be the minimal code required to reproduce the problem you are describing. Or you should tell us that your issue can not be reproduced. The code you show us now [produces these errors](https://godbolt.org/z/n9Tozxjs7), which seem to be unrelated to your question. – Drew Dormann Jun 25 '22 at 21:42
  • 1
    It seems to me that, by definition, ItemInventorySlotUIComponent is not an abstract base class because you have implemented the virtual functions. There is no default constructor for UIComponent and since ItemInventorySlotUIComponent can be instantiated, the UIComponent constructor must be called somehow when it is instantiated. – Andrew Jun 25 '22 at 21:43
  • 1
    No, we want you to paste a [mre]. Any compilation error can be demonstrated with no more then 20-30 lines of code. In some extreme case more, when it involves some hairty parts of C++, like templates and specializations If you don't make a minimal amount of effort to clearly demonstrate your problem, and make it as easy as possible for everyone to cut/paste, reproduce, and investigate it themselves, why should anyone make the effort to help you? – Sam Varshavchik Jun 25 '22 at 21:44
  • @Andrew Yes, that is exactly my problem. Do you know if there is any way to make the class abstract without declaring any pure virtual functions? I will create an ItemInventorySlotUIComponent object, only instances of its subclasses. – Promitheas Nikou Jun 25 '22 at 21:44
  • @PromitheasNikou -- The simple way to produce an [mcve] is to remove all of the noise from the code you posted. For example, we don't care about 4 different click functions in `UIComponent` -- a simple `virtual bool foo() { return false; }` and a lone member would have sufficed. We don't care that you are inheriting from 3 classes -- a simple inherit from a dummy class would have sufficed. Do that in the other places, and it is guaranteed you can duplicate the error with less than half the currently posted code, and have that code be minimal. – PaulMcKenzie Jun 25 '22 at 21:53
  • Wait, that's what I'm doing right now... – Promitheas Nikou Jun 25 '22 at 21:54
  • Done. Will you please at least try to help now? – Promitheas Nikou Jun 25 '22 at 22:03
  • 1
    I believe you can make the destructor of your derived class pure virtual, which will make it an abstract base class (TargetClass in the revised question) – Andrew Jun 25 '22 at 22:05
  • Yeah, sorry, fixed it now – Promitheas Nikou Jun 25 '22 at 22:07
  • @Andrew Yes, I could do that, but then I'd have to provide a definition for the destructor for every subclass. That is a solution, but is there no other cleaner and prettier way? – Promitheas Nikou Jun 25 '22 at 22:08
  • *"I want this class to be abstract (not be able to create instances of it, but also not needing to call the ctor of the virtual base class)."* -- I think you have this backwards, swapping what you want with how you tried to get what you want. You want to not be able to create instances of this class, and also not need to call the constructor of the virtual base class (in this class' constructor?), and you believe making the class abstract would accomplish this, right? – JaMiT Jun 25 '22 at 22:09
  • @Promitheas have you checked that that is true? Does the default destructor provided by the compiler not satisfy this requirement? (I've never tried, so can't say) – Andrew Jun 25 '22 at 22:10
  • @PromitheasNikou *"I'd have to provide a definition for the destructor for every subclass"* -- it is traditional to provide a destructor definition in every derived class, regardless. It's a good idea to not only provide a destructor, but also go as far as marking the destructor `override` to ensure that the base class' destructor is virtual. (E.g. `~ClassName() override = default;`) – JaMiT Jun 25 '22 at 22:12
  • @PromitheasNikou `ClassB() {} //I don't want to call the CommonBaseClass ctor` -- But you see that you *are* constructing a `ClassB` object, and the parent object must be constructed for the derived object to exist. That's how C++ works -- there is no such thing as deferred construction, where you defer the construction of the parent at a later time. Whatever your original "solution" was, it was no solution right from the start. Maybe you should rethink your design? – PaulMcKenzie Jun 25 '22 at 22:16
  • The question "Why do we need a pure virtual destructor?" is not really a duplicate, but these two answers seem relevant: [answer 2](https://stackoverflow.com/a/1219984/); [answer 3](https://stackoverflow.com/a/1219735/) – JaMiT Jun 25 '22 at 22:19
  • @PaulMcKenzie Since C++11, it is legal for a constructor of an abstract class to omit an initializer for a virtual base class, even if the base class cannot be default-initialized. When a constructor of an abstract does have an initializer present for a virtual base class, it must be well-formed, but will never be evaluated. – aschepler Jun 25 '22 at 22:40

2 Answers2

3

It's true (since C++11) the constructors of an abstract class don't need to initialize their virtual base classes. The usual way to make a class abstract without any specific unimplemented virtual functions is to make the destructor pure virtual.

With this change, your program compiles:

class TargetClass : //I will never instantiate this class
    public ClassA,
    public ClassB
{
public:
    TargetClass() : ClassA(), ClassB() {} //I don't want to call the CommonBaseClass ctor

    virtual ~TargetClass() = 0;
};
TargetClass::~TargetClass() = default;

Note the destructor must be defined, even though it is pure. Since one declaration can't have both = 0 and a {body} or = default, that destructor definition in this case needs to be outside the class definition.

Though actually, it's recommended to always make sure any polymorphic class (having at least one virtual function or virtual base class) has a virtual destructor. This makes it legal to delete a pointer of base class type pointing at an object with a different dynamic type. And with the usual vtable implementations, there's nearly no cost to add the virtual destructor to a class which is already polymorphic. Following this advice, you would also add virtual ~CommonBaseClass() = default; to CommonBaseClass, and then the other classes automatically also have virtual destructors. And then you might use the style ~TargetClass() override = 0; for the declaration of the pure virtual destructor.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Wait, so a method can both be pure and virtual, and have a body? (thanks for the answer, but andrew had already written a comment about this, so it wouldn't be right not to accept his...) – Promitheas Nikou Jun 26 '22 at 05:12
  • 1
    Yes. In the destructor case, the only actual effect is to make the class abstract, and the destructor body will be executed like usual after the derived destructor. You're also allowed to define any other pure function, and its body can be executed if the virtual function is called during the base constructor or destructor, since the object doesn't really have the derived type yet / any more. – aschepler Jun 26 '22 at 11:59
  • OK Thanks! Is this a good practice or is this a quick fix, because it seems a little counter-intuitive? – Promitheas Nikou Jun 26 '22 at 16:20
2

You can use a pure virtual destructor as follows. Even although it is pure virtual, you still need to implement it. You do not need to declare destructors in the derived classes.

class TargetClass : //I will never instantiate this class
    public ClassA,
    public ClassB
{
public:
    TargetClass() : ClassA(), ClassB() {} //I don't want to call the CommonBaseClass ctor
    virtual ~TargetClass() = 0;
};

class SubclassOfTarget :
    public TargetClass
{
public:
    SubclassOfTarget(int a) : CommonBaseClass(a), TargetClass()
    {}
};

TargetClass::~TargetClass()
{}

int main()
{
    SubclassOfTarget c(15);
    c.DoSomething();
    c.DoSomethingElse();
}
Andrew
  • 721
  • 4
  • 14
  • Wait, so a method can both be pure and virtual, and have a body? (btw I accepted your answer, because you had already said written it in the comments yesterday) – Promitheas Nikou Jun 26 '22 at 05:14
  • Thanks. Apparently so. It seems this applies only to destructors, providing the mechanism you were looking for. I found an article about this here https://www.geeksforgeeks.org/pure-virtual-destructor-c/ – Andrew Jun 27 '22 at 04:28