3

I'm making a library which requires that classes must inherit other classes to do something specific. However, this is not simple polymorphism. These classes are code generators of virtual functions, with no data and which rely on CRTP, so they themselves don't need a vtable.

Is there a way to stop the vtable from being emitted for these classes? I'd assume that the virtual function pointers would be passed the the derived class, and the virtual destructor would just skip over these classes. Sort of like melding the classes together into one.

If nothing general is available across the C++ domain, then maybe specific to clang, gcc and vc?

Example:

#include<iostream>

template <typename D, typename B>
struct jelly : B
{
  virtual void do_stuff() { static_cast<D*>(this)->D::do_some_other_stuff(); }
};

template <typename D>
struct jelly<D, void>
{
  virtual void do_stuff() { static_cast<D*>(this)->D::do_some_other_stuff(); }
};

struct A : jelly<A, void>
{
  void do_some_other_stuff() { std::cout << "A::do_some_other_stuff()\n"; }
};

struct B : jelly<B, A>
{
  void do_some_other_stuff() { std::cout << "B::do_some_other_stuff()\n"; }
};

int main()
{
  A a;
  a.do_stuff();  // output: A::do_some_other_stuff()
  B b;
  b.do_stuff();  // output: B::do_some_other_stuff()
  A& aa = b;
  aa.do_stuff(); // output: B::do_some_other_stuff()
}

Just to clarify, this is just an example. It does run, but the number of classes that jelly represents is actually 3 different ones. One that is inherited explicitly by the dev using the jelly library, and 2 others that are done implicitly, before inheriting back into the dev's own classes. It is because of the number of classes would increase 3x that it has become to worry me, and is why I am asking this question.

Community
  • 1
  • 1
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • 7
    [This sounds like an XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What are you really trying to do? – selbie Apr 13 '19 at 21:47
  • Also, it would help if you could explain your class inheritance model better and give some simple examples. – selbie Apr 13 '19 at 21:48
  • 5
    Like @selbie said, this sounds like it might be an XY problem. However, if what you're asking is why the compiler generates a vtable even when polymorphism isn't used, the answer is that the compiler *doesn't know* it's not used, particularly because this class could be used in other translation units, for example. Why do you want to stop the compiler from generating the vtable? If it's for efficiency of some sort, then a) this sounds like it could be premature optimization, and b) that would be removing unused symbols, which is something that probably would be done at the linker stage. – Nick Mertin Apr 13 '19 at 21:51
  • Don't put in `virtual` and no vtable will be generated. – Eljay Apr 13 '19 at 22:07
  • The reason for this is to be able to insert duplicate code into the hierarchy that can also be specialized, based on some compile time information. I'm not premature optimizing, but I am thinking ahead as having this layout may result in a considerable increase in vtables which will never be used. – Adrian Apr 13 '19 at 22:07
  • Thanks @Eljay. Brilliant! Why didn't I think of that? :eyeroll: – Adrian Apr 13 '19 at 22:09
  • 3
    "thinking ahead" for something that "may" happen is the literal definition of premature optimization. – selbie Apr 13 '19 at 22:09
  • @selbie, as this **would** increase the number of vtables for a hierarchy by **3x**, I don't think "may" comes into it. Especially in low memory systems. – Adrian Apr 13 '19 at 22:11
  • Also @selbie, I'm telling the compiler that it won't be used in other transition units. – Adrian Apr 13 '19 at 22:14
  • 1
    You may choose a compiler that does not gen a 'vtable' ... the 'vtable' is an implementation choice, not a language requirement. – 2785528 Apr 13 '19 at 22:15
  • 1
    But you never stated your performance goals or why you think having an extra number of vtables is a bad thing. Is it to reduce binary sizes of the linked code? Now that you have some possibilities as suggested by others, have you measured the differences? – selbie Apr 13 '19 at 22:16
  • Reduced size is the main concern. Deletions would be done a lot, so not traversing through unnecessary destructors would be a plus, especially in complex hierarchies. As I do not know where this may be used, I can't predict how big a change in performance or memory would be. But it is something that I would like to keep an eye on, and if I have some possible ways to stop these things from becoming an issue ahead of time, then I see no reason why I shouldn't take a look ahead of time. – Adrian Apr 13 '19 at 22:29
  • @NicolBolas, yeah, right. I made a mistake. I'll correct. – Adrian Apr 13 '19 at 22:29
  • It is now in a runnable state, if that matters. – Adrian Apr 13 '19 at 22:41
  • I'm curious as to why you do not want a vtable emitted, since the code currently relies on the vtable, in the `aa.do_stuff();` invocation. – Eljay Apr 13 '19 at 22:46
  • @Eljay, it relies on the topmost vtable. I.e. the vtable of `B`, which includes the function pointer to `jelly::do_stuff()`. There will never be a cast to `jelly`, only the external types (in this case `A` and `B`). – Adrian Apr 13 '19 at 22:52
  • @2785528, that's always a possibility, but in any case, I would still like to tell the compiler that object of this type are never to be casted to this type. Do no allow and based on that, don't add any extra overhead (memory or code) that is not needed. – Adrian Apr 13 '19 at 23:04
  • @Adrian: The problem I have with this code is that, for it to make sense for `jelly::do_stuff` to be virtual, you must have some code, *somewhere*, that takes only a pointer/reference to a `jelly` and calls `do_stuff` knowing *only* about `jelly`. But you don't actually have that, because anyone who knows about `jelly` *by definition* must also know about `jelly`'s template parameters. There's no reason for it to take a pointer/reference to `Jelly` when it could just take `D` and cut out the middleman. So why is `do_stuff` virtual? – Nicol Bolas Apr 14 '19 at 01:40
  • @NicolBolas, there are some helper functions that take `jelly`, but only to consolidate code, and that code doesn't require access to the vtable. `do_stuff` is virtual only so that it can determine where in the hierarchy it is and call the appropriate functions, constructors and destructor at hat level. It'll be documented in the library that these functions (like `do_stuff`) should never be overridden. The very specific nature of the functions are such that doing so would just break the library. – Adrian Apr 14 '19 at 02:07
  • 1
    A class which has a virtual function has a virtual table. It's in e every C++ ABI out there. It isn't clear why you need anything to be virtual though. Is `do_stuff` ever overriden? If not, it need not be virtual. If yes, you need the virtual table machinery for it. – n. m. could be an AI Apr 14 '19 at 03:15

3 Answers3

4

The only known to me compiler extension to do this is MSVC's __declspec(novtable):

This form of __declspec can be applied to any class declaration, but should only be applied to pure interface classes, that is, classes that will never be instantiated on their own. The __declspec stops the compiler from generating code to initialize the vfptr in the constructor(s) and destructor of the class. In many cases, this removes the only references to the vtable that are associated with the class and, thus, the linker will remove it. Using this form of __declspec can result in a significant reduction in code size.

If you attempt to instantiate a class marked with novtable and then access a class member, you will receive an access violation (AV).

This modifier is implied when you use MSVC's __interface keyword.

2

If you declare a member function to be virtual, that class must have whatever machinery the implementation deems necessary to accomplish what C++ requires that virtual functions do. But this also means that the type is now polymorphic, which requires that the type be able to do what C++ requires that polymorphic types can do. Specifically, typeid and dynamic_cast.

That's important. A class which derives from a polymorphic type is itself polymorphic, whether it overrides any virtual functions or not. This means that you must be able to get the type information from an instance of that class. Whether you actually do it is irrelevant; you could, and therefore the machinery must exist to allow it.

For vtable implementations, this typically means that every polymorphic type needs to have a unique vtable object. The vtable would have an index or pointer to some type-specific information, in addition to the pointers to virtual functions. Since vtables tend to be pretty tiny, having another vtable around isn't particularly onerous. Indeed, the type identification information itself is generally more significant than the vtable.

Now, compilers have options that allow you to remove all vestiges of run-time type identification. Specifically, typeid doesn't work anymore, and dynamic_cast never throws and thus needs not verify the cast, it is no longer necessary for the compiler to give A a different vtable from jelly<A>. However, the main target of the feature is the table of type_info objects and other identifying information. So I can't speak to the effects of this feature on vtable generation.

Ultimately however, there isn't much you can do. Those classes are probably going to get vtables, and that's that.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
0

You do:

template <typename D, typename B>
struct jelly : B
{
  virtual void do_stuff() { D::do_some_other_stuff(); }
};

Then you do:

struct B : jelly<B, A>
{
  void do_some_other_stuff() { std::cout << "B::do_some_other_stuff()\n"; }
};

Which means that struct B depends on jelly<B, A>. That, in turn needs to call a B(aka D) function do_some_other_stuff, but B is not yet defined. Maybe you need to move void do_stuff outside of the class declaration. Anyway, it shouldn't get inlined as you're declaring it virtual.

Also, you're using

struct A : jelly<A, void>

Which translates to:

struct jelly : void
{
  virtual void do_stuff() { A::do_some_other_stuff(); }
};

How do you expect to inherit from void?

Style note: don't use struct just to avoid public. Don't use typename D when there should be a class (because you're inheriting) and not just any typename. Don't use names A, B, C, D, type some more descriptive. Is struct jelly : B inheriting from typename B or from struct B?

Mirko
  • 1,043
  • 6
  • 12
  • 2
    The example is just a simplified example. Yes, I know that in the example, I could do without the CRTP and just go right for the virtual inheritance. `jelly` is just a placeholder that can be specialized based on particular criteria that is not known until it is compiled. I'm also not inheriting from void. Please look at the specialization of `jelly` when it's template parameter `B` is `void`. As for style, I tried to make the example as short as possible, so I don't think that is a big deal. – Adrian Apr 13 '19 at 22:21
  • 1
    @Adrian It's not a big deal; but it takes away my focus from what you actually asked. Regarding that, the only way not to have vtable i can think of is not have any virtuals. – Mirko Apr 13 '19 at 23:05
  • Thanks, but I think that this 'issue' has come up before somehow. When classes are inherited, but don't contain any data or will never be casted to, I would think that there could be some manner to give that information to the compiler so that it can make additional optimizations. – Adrian Apr 13 '19 at 23:10
  • If you inherit from them, the compiler needs to see the complete declaration (forward declaration will not do). But you could try to move function implementation outside class definition (move `do_stuff` implementation outside class declaration). – Mirko Apr 13 '19 at 23:13
  • No, I need to know generically, the type in hand in order to do the correct thing with as little overhead as possible. Inheritance is the way to go. – Adrian Apr 13 '19 at 23:25
  • @Adrian: "*will never be casted to*" Yes, it will. It happens implicitly whenever you do `b.do_stuff()` or `a.do_stuff()`. Part of the process of making that function call is the conversion of the pointer to `B` or `A` into a pointer to the appropriate `jelly` instantiation. – Nicol Bolas Apr 14 '19 at 02:04
  • Ok @NicolBolas, I apologise. My terminology isn't as accurate as I'd like it to be. Yes it will be implicitly casted, but due to the nature of the problem, I can see a path where the information about `jelly` isn't needed because it doesn't reference any data in that unit, simply because there *is* no data in that unit. Any required data would be from the class using the `jelly` template library, and only from the member functions required. – Adrian Apr 14 '19 at 02:42