3

I have a Base class and a Derived class. The only goal of the Base class is to make sure Derived implements a member function.

struct Base
{
    virtual void f() = 0;
};

struct Derived : Base
{
    void f() override final {}
};

I don't use this class polymorphically, i.e., I just instantiate objects of type Derived on the stack, like this:

Derived obj;

and I need to do this millions of times.

Edit: only a few instances exist at the same time (no stack overflow).

Is a vtable created here (during compilation time I guess)? Does it matter to me if a vtable is created or not, since I don't use it (or do I somehow)? Is there any overhead I should consider using this design? Maybe there is another way to make sure the compiler complains if Derived doesn't implement f()?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
mfnx
  • 2,894
  • 1
  • 12
  • 28
  • Does this answer your question? [Is final used for optimization in C++?](https://stackoverflow.com/questions/37414995/is-final-used-for-optimization-in-c) – Moia Jan 17 '20 at 08:24
  • 1
    Why not simply omit `f()` in `Base`? If `Derived` doesn't implement `f()`, `obj.f()` will fail to compile. – Evg Jan 17 '20 at 08:27
  • 2
    You can check the size of `Derived`. In my case, [it was 8 bytes](https://wandbox.org/permlink/nJmdEfWBoawmbGFz), which implies a vtable pointer. _Maybe there is another way to make sure the compiler complains if `Derived` doesn't implement `f()`?_ Sure, if you call `f` for any object of `Derived`, without `f` provided, the compilation will fail. Also look at: [Is it possible to write a template to check for a function's existence?](https://stackoverflow.com/q/257288/580083). You can write a metafunction for checking existence `f` and use it, e.g., with static assertion for better diagnostic. – Daniel Langr Jan 17 '20 at 08:31
  • 1
    If you don't use class polymorphically, you may use CRTP pattern which a) without additional arcane passes and whistles would ensure need of implemented method b) doesn't use vtable and may result in optimized out calls – Swift - Friday Pie Jan 17 '20 at 09:01
  • @Evg good point, but imagine this code is in a library and called only from another library. – mfnx Jan 17 '20 at 09:14
  • @Swift-FridayPie not sure how the compiler would complain with CRTP if you don't implement f() in the derived class. – mfnx Jan 17 '20 at 09:40
  • @mfnx attempt to `static_cast(this)->f()` will result in compile-time error. There is extended way to create CRTP which provides fallback function, but it's not part of pattern. – Swift - Friday Pie Jan 17 '20 at 17:37
  • @Swift The base class would be template struct Base{...}; This compile just fine. If I'm wrong, please post an answer with your solution. Their would be a runtime segfault, not a compiler error imo. – mfnx Jan 17 '20 at 20:20

1 Answers1

3

Is a vtable created here ?

Yes since you have virtual member functions.

Does it matter to me if a vtable is created or not, since I don't use it ?

Since you don't use it, it still matters in the sense that it will increase the size of your Derived structure.
Here your Derived structure is of size 8. But without the vtable, it would be of size 1.

Maybe there is another way to make sure the compiler complains if Derived doesn't implement f()?

To be honest, I think your solution using Base as an interface in order to force every derived class to implement the f() function is totally fine since it is the exact use-case for the use of interfaces.


But if the size of the Derived structure is a concern (because you said you wanted to instantiate it millions of times), perhaps you would be interested by the std::is_member_function_pointer type trait.

I have no idea about how you intend to instantiate your Derived structure so I cannot provide a code that would exactly suit your needs.
But the idea I'm thinking about is equivalent to the following (generic example):

 #include <type_traits>

template <typename T>
void instantiate_a_lot_of_times(std::size_t nb_times)
{
    // Check if the f() member function exists
    static_assert(std::is_member_function_pointer<decltype(&T::f)>::value, "Error: The T::f() member function must be defined");

    for(std::size_t i = 0; i < nb_times; ++i)
    {
        T t;
        // Do something with t
    }
}

But keep in mind that this approach has the drawback of delaying the check.
The compilation will not fail when the structure definition is encountered but when the static_assert is evaluated.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • I added an edit to my question: only a few instances exist at the same time. – mfnx Jan 17 '20 at 12:22
  • I don't think allocating 8 or 1 on the stack is important performance wise, is it? – mfnx Jan 17 '20 at 13:04
  • @mfnx In this case, I think your approach should be fine/is the good one. I did some benchmarks over `10e7` iterations (1 iteration: instantiate 3 `Derived` objects and call `f()` on each instance). With and without the vtable (size 8 vs size 1), I did not see any significant time difference (a few hundredths of a second, on my machine, which is not very powerful). – Fareanor Jan 17 '20 at 13:17