2

I think I am misunderstanding something with the C++ inheritance.

Let's say I have this classical inheritance :

    class A{
    public: 
        virtual void method1() =0;
        virtual ~A() = default;
    }

    class B : public A{
    public :
        void method1(){doSomething();}
    }

    class C : public A{
    public :
        void method1(){doSomethingElse();}
    }

How do I make an other Class Q own an object of type B or C without knowing in advance which type it is ? (I mean we know it is type A that's all)

Thanks for reading !

Cryckx
  • 659
  • 6
  • 18

2 Answers2

5

Since you do not want Q to know about B and C, you have to store the A indirectly by a pointer, since you cannot know how much storage B, C or a yet unknown derived class D will need. The classic solution for a single object that is allocated dynamically and owned by Q is a std::unique_ptr.

Since std::unique_ptr by default uses delete to delete the object, you also need a virtual destructor in A.

#include"A.hpp"
#include<memory>

class Q {
    std::unique_ptr<A> a;
public:
    Q(std::unique_ptr<A> ai) : a{std::move(ai)} {}
};

If you do not want exclusive ownership of Q over the A, or do not want to provide a virtual destructor, you can also use a std::shared_ptr. Shared pointers remember the stored class dynamically and thus destroy the object correctly if they are created appropriately, notably with std::make_shared<MostDerivedClass>. Also they use reference counting and will delete the object only after all references to the object are gone. The downside is a bit of extra memory needed and some runtime cost, compared to std::unique_ptr which usually has no overhead compared to a plain pointer.

PaulR
  • 3,587
  • 14
  • 24
  • 4
  • @user2079303: thank you, that is correct, I edited the answer. – PaulR May 30 '18 at 14:59
  • Additional note, which may be a bit off topic: By declaring the virtual destructor, the implicit move constructor/assignment will be suppressed, and copy operations will be used for moves. This is highly undesirable if `A`, `B` or `C` contain members that are slow to copy but fast to move. A good rule of thumb since C++11 is to always declare defaulted move operations whenever a destructor is declared. – eerorika May 30 '18 at 16:15
  • @user2079303 when there is a plan to use objects of A B and C polymorphically and so to manage those dynamically through smart pointers then only sane thing is to disable both move and copy of those classes and to use move and copy of pointers instead. – Öö Tiib May 31 '18 at 08:14
  • @ÖöTiib disabling move of the class would be just silly. It's trivial to implement efficiently, and very useful. Copy would be disabled automatically by having a unique pointer member, and I would agree that there's no need to go out of your way to implement it. – eerorika May 31 '18 at 21:00
  • @user2079303 on case of OP where the owner of B does not want to know if it is B or C not disabled move can only cause defects. Disabling source of defects is not silly. – Öö Tiib Jun 03 '18 at 08:54
3

A class must know exactly the type of all its sub-objects. Dynamic polymorphism is possible only through indirection. As such, while it is not possible contain an object of polymorphic type, it is possible to own one. Example:

struct Q {
    shared_ptr<A> object; // could point to B or C
}
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • The shared_ptr is not made for owning but for shared ownership. Massive usage of is as owning smart pointer is preliminary pessimization that makes lot of code pointlessly inefficient. – Öö Tiib May 31 '18 at 07:34
  • @ÖöTiib Not true. Shared pointer is in fact made for owning. Shared owning more specifically. Sure it may be a bit slow depending on use case, but it's also by far the simplest way to own a polymorphic object that lacks a virtual destructor. – eerorika May 31 '18 at 21:04
  • So you repeat what I wrote. Usage of shared_ptr when there are no need for shared ownership is just confusing. Add the virtual destructor always to polymorphic class. Modern compilers avoid calling it through vtable when possible. It just makes the hierarchy more reliable. Say further analysis shows that Q needs not one A but collection of As. So programmer takes boost::ptr_vector but forgets to check that A's destructor is virtual and has undefined behavior. – Öö Tiib Jun 03 '18 at 09:12