Virtual function's disadvantage and real-life examples

I heard Casey said in one of his clip is that he hate virtual function because it's not serializable, can't change at runtime, can't tightly pack, etc. I don't really understand what did he mean when he said it's not serializable? And why do you need it to be? Also, some real-life examples for the need of changing at runtime, pack tightly, etc, and the way you achieve it will be helpful.

Edited by longtran2904 on Reason: Initial post
Not serializable means you cannot memcpy it to byte buffer for saving/restoring later, or for saving to file, or transmitting over network. Because those bytes will include pointer to function addresses that may be not valid when exe is run later / different computer. Technically you don't "need it", but it makes a lot of things simpler when you can simply memcpy structures around.
Is it because the vtable is a compiler implementation detail and you can't access it? So if you try to memcpy it, does it still copy the virtual function pointer? If I make my own vtable then it still may not be valid when run later or on a different computer.
Yes, that is correct. Not only vtable pointer itself can change, but also all the methods it points to can change. So there's a lot of patching involved if you want to memcpy it.
Another disadvantage that Casey pointed out is that you can't change it at runtime. I can't really think of any reason to do so. Do you have any real-life examples?
when you want to recycle an object's allocation but as a different subclass.

That is technically possible using placement new but requires that you give each subclass enough memory for the largest one. But there are zero checks on that in C++ so accidentally going out of bounds is far too easy.

There's something I still don't understand. When I make a virtual function, the compiler adds a function pointer to the vtable at the beginning of my struct. When I memcpy it, technically it still copies the function pointer, right? So why isn't it serializable? And why make my own vtable would solve it? Also does adding a virtual function increase my struct size?
Because when you run your exe next time, OS loads it into different location - so all your pointers to functions (and global variables/data) are now invalid.

In simplest case if you have virtual function in your struct (or any of its base classes) then struct will have only one extra pointer at beginning. It will point to virtual table that has as many entries as there are virtual functions. So adding extra virtual function will increase count of this table/array, but not struct itself. It gets more complicated with multiple & virtual inheritance.

Edited by Mārtiņš Možeiko on
But if I make my own vtable then it still not solve this. The next time I run the program, all the pointers will point to different locations.
That is the point of this - to not create vtables to allow memcpy'ing around. Usually alternative to virtual tables in C are discriminated unions. You have struct with enum type and union of all possible types. Then you switch based on type. This way you can memcpy it around because type is just an integer in memory:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
enum Type { Cat, Dog };
struct CatStuff { ... };
struct Dogtuff { ... };

struct Animal {
   Type type;
    union {
      CatStuff cat;
      DogStuff dog;
   };
};

Of course you don't need to do that for whole contents of struct. Sometimes just one "SomeType type" member is enough without any unions. Then you simply do switch on it at runtime to call appropriate function - this way resolving function call addresses will happen at runtime, in place where switch is and not stored inside struct.
This is most trivial way to do it. Other way is to design your structs to have everything you need ("fat structures"), or have array with variable amount of properties that you access individually.

Edited by Mārtiņš Možeiko on
Ok, I understand it now. Thank you for answering!
Another thing that I heard is that using virtual can get better type safety and some performance improvements, such as the ability to use inline functions. Do these advantages help you much? Are these a huge loss when you don't use virtual?
Not sure what you mean by type safety. Types will same in any case - whatever types you write, regardless of virtual methods or not.

As for inlining, it is actually opposite - it is worse when you use virtual methods. Because when compiler sees virtual method call it cannot easily decide to inline it or not if it does not now two things - exact type of object, and whether this method is overridden in some inherited type. It can inline only if it knows both things. Compilers try very hard to figure out this stuff, it is called devirtualization - that allows to inline more and often get better performance. C++11 even added new keyword to help there - final. It allows you to tell compiler that this method won't be overridden in inherited classes, so it helps it to figure out when to inline more. Basically now you need to do more work to make compiler output better code.

For normal functions decision to inline is easy - if compiler sees function definition it can decide to inline it, and if it does not see, then it cannot. Simple as that.

Edited by Mārtiņš Možeiko on
Using virtual just put a hidden pointer to a hidden vtable somewhere, right? Why does the compiler need to know what's the object's type is and whether it override some method? Why doesn't it just use the function pointer at the beginning of the object?
longtran2904
Using virtual just put a hidden pointer to a hidden vtable somewhere, right? Why does the compiler need to know what's the object's type is and whether it override some method? Why doesn't it just use the function pointer at the beginning of the object?


the compiler will want to inline the function call if possible, which it cannot if it doesn't know the runtime dynamic type. It needs to know exactly which function definition it will jump to at runtime to inline.

doing the inlining means it can remove the load of the vtable and the dynamic jump and it can use the constraints of the input parameters to further optimize the function's code itself that way for example bound checks can be deduplicated, or computations with constant parameters can be constant folded, etc. At the same time the constraints that the output of the function has can be used to further optimize the code after the function call.