-3

I have a class A and class B which both have the same methods and variables, but B has one additional variable (which is completely independent from other class members). So it would be something like:

class A
{
    void Foo();
    bool m_var;
}

template< class T >
class B< T >
{
    // Same stuff
    void Foo();
    bool m_var;

    // Unique stuff
    T m_data;
}

Normally, I would use inheritance B : public A, but I want to keep these classes super tight and I don't want to have vtable ptr inside them (as I'm not gonna use polymorphy anyway). What's the best approach to achieve that? I was thinking about templates and their specialization - having class A<T> and A<void>, but I need to remove something, not add. Is there any smart template trick which I could use? I was also thinking about creating base class (without virtual dtor) with all common functionalities as a private nested class and inherited classes A< T > : public Base and empty B : public Base as public nested classes. It wouldn't allow anyone from outside to use base class ptr, but it doesn't sound like the purest solution... Is there any "valid" solution for my problem?

Smith
  • 319
  • 2
  • 12
  • 3
    inheritance != polymorphism. – 463035818_is_not_an_ai Apr 30 '21 at 21:23
  • 1
    [_Static Polymorphism_](https://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp) may be? – πάντα ῥεῖ Apr 30 '21 at 21:25
  • 2
    `B : public A` doesn't by itself introduce a vtable pointer. Not until you actually add a virtual member function. – Igor Tandetnik Apr 30 '21 at 21:27
  • 1
    A "vtable" is for virtuals. A simple inheritance doesn't imply virtuality. – max66 Apr 30 '21 at 21:27
  • But is it fine to create a base class without virtual dtor? Isn't it dangerous for potential users? – Smith Apr 30 '21 at 21:28
  • 3
    Virtual destructor is only necessary if you're planning to reference a subclass through a pointer to the parent (i.e. use the virtual polymorphism you're trying to avoid). If you're not planning to do that, then a non-virtual (or defaulted) destructor is harmless. – Silvio Mayolo Apr 30 '21 at 21:30
  • 1
    if you use private inheritance then there is not even the danger of someone wrongly assuming it would be polymorphic – 463035818_is_not_an_ai Apr 30 '21 at 21:31
  • Isn't private inheritance changing members visibility to private? – Smith Apr 30 '21 at 21:32
  • 1
    More than that. It prevents anyone except the derived class from being able to take advantage of the is-a relationship between base and derived and get you into trouble where you need the `virtual` destructor. Demo: https://ideone.com/J7VVVI Of course this is C++, so you can always shoot your leg off if you want to go out of your way. – user4581301 Apr 30 '21 at 22:20

2 Answers2

3

As long as you don't use the word virtual, you won't get a vtable. But you're right to think that inheritance is an awkward solution to this problem. Languages like Rust, Scala, and Haskell have a unit type () for data we don't care about. C++ approximates this (albeit poorly) with void, but it really only works as a function return type. My recommendation is to create your own well-behaved unit type.

struct Unit {};

Nice, empty type. There's only one meaningfully distinct instance of Unit, namely Unit(). Then A is just B<Unit>. The Unit m_data in B<Unit> contains no actual information and will likely be optimized out by the compiler.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
0

As already answered, vtable pointers are only added to a class when they are needed. As long as there is nothing marked as virtual anywhere in either your base or derived class, the compiler will not generate a vtable for your type.

If you want to reuse A when writing B, why can't you just use plain old composition?

class A {
    bool _var;

public:
    explicit A(bool v);

    void foo();

    // accessors, which could be constexpr
    bool var() const;   // get
    void var(bool n_v); // set

};

template<class T>
class B {
    T _data;
    A _a;    

public:
    B( /* params */);

    void foo() { this->_a.foo(); }
    
    // forward to A
    bool var() const { return this->_a.var(); }     // get
    void var(const bool n_v) { this->_a.var(n_v); } // set

    // add more stuff here
};

If you really are into nasty shenanigans, you might consider taking the private inheritance route, which is functionally the same thing (but less clean, IMHO):

template<class T>
class B : private A {
    T _data;

public:
    B( /* params */);

    using A::foo; // reexports A::foo() as B::foo()
    
    // generates
    // - bool B::var()
    // - void B::var(bool)
    using A::var; 

    // add more stuff here
};
mcilloni
  • 693
  • 5
  • 9