[C++] virtual member functions in a templated class

Started by
24 comments, last by phresnel 15 years ago
Quote:Original post by phresnel
Quote:Original post by bpoint
By simply adding virtual to the class's functions, the whole template becomes instanctiated, and all types of T are now required to implement everything the template requires even if they'll never use them.

Argh, yes, me and my bad Engrish. It happens when you live in a foreign country and only speak in your native tounge once a month to call home.

I suppose I should have said something like:

"By simply adding virtual to the functions in the class which I plan to derive from, those particular functions in the template incidentally become instanctiated as well, and all types of T are now required to implement the functions and operators used by the virtual template functions even if they'll never use them."

Better? :)

Quote:Original post by Humble Me
While not impossible, it would cause a lot of epic PITA to manage a partially initialized, partially uninitialized vtable. It could cause PITA for the compiler, but could also end in performance trouble at runtime.

Yes, I didn't forget you said this. Again, I'm no compiler writer, but I still don't think it would be that difficult to maintain a partial vtable, which would have no affect on runtime performance. If you can show me where I am wrong in my thinking, please do. :)
Advertisement
There is probably a reason why the STL containers are not all derivates of the same base class. They are simply too different to be able to support the same interface. They are all similar to use, and that's largely thanks to iterators.

If you need things like dummy operator== (which just introduces potential bugs to anything using these classes) you must be on the wrong track.

Runtime polymorphism:
template <class T, class U>void foo(BaseContainer<T>& container, const U& value){    container.add(value);}int main(){    SortedArray<int> sorted;    foo(sorted, 1);    FixedInMemoryArray<int> fixed;    foo(fixed, 1);}


Same thing with templates:
template <class Container, class T>void foo(Container& container, const T& value){    container.add(value);}int main(){    SortedArray<int> sorted;    foo(sorted, 1);    FixedInMemoryArray<int> fixed;    foo(fixed, 1);}


Pretty much the same thing, except the compiler can catch errors when assignment operator is required but missing (as you don't need to cripple your classes with non-working functionality).
Quote:Original post by bpoint
Quote:Original post by phresnel
Quote:Original post by bpoint
By simply adding virtual to the class's functions, the whole template becomes instanctiated, and all types of T are now required to implement everything the template requires even if they'll never use them.

Argh, yes, me and my bad Engrish. It happens when you live in a foreign country and only speak in your native tounge once a month to call home.


Actually, you're english is pretty good, and that was the reason why I assumed that you are wrong, and didn't assume that it was just bad wording :D

Quote:I suppose I should have said something like:

"By simply adding virtual to the functions in the class which I plan to derive from, those particular functions in the template incidentally become instanctiated as well, and all types of T are now required to implement the functions and operators used by the virtual template functions even if they'll never use them."

Better? :)


Hehe, indeed. Or shorter: "Making a class-template-member-function virtual causes that function to be instantiated eagerly (as compared to the lazy instantiation of non-virtual class-template-member-functions)." :)


Into more detail about why partially initialized vtables can cause PITA


Quote:
Quote:Original post by Humble Me
While not impossible, it would cause a lot of epic PITA to manage a partially initialized, partially uninitialized vtable. It could cause PITA for the compiler, but could also end in performance trouble at runtime.

Yes, I didn't forget you said this. Again, I'm no compiler writer, but I still don't think it would be that difficult to maintain a partial vtable, which would have no affect on runtime performance. If you can show me where I am wrong in my thinking, please do. :)


The problems are mainly the same as the problems for implementing the keyword "export" ("Why we can't afford export"), which is only implemented in one and only one C++ compiler frontend.

Most easily understood might be that C and C++ have so called "translation units", where a unit is, simply speaking, a single sourcecode file (as compared to header files). Virtually all C and C++ compilers will compile each single sourcefile into a corresonding, so-called "object file", which contains machine code (i.e. it contains compiled code; that code is not necessarily actual machine code), plus a directory of entities (functions, classes) that can be found in that object file. Finally, you pass all object-files to the "linker", who will assemble a working program from those, based on the information that can be found in the directories. At that final level, it is hard (as in PITA), not impossible, to do further optimizations and things like keeping track of the initialization status of vtables upon binary data.

To give an exemplary and heavily eased example for how compiling works in C and C++:

sourcefile-a.cpp
extern int alphaCentauri () ;int main () {    alphaCentauri ();} 



The compiler has only access to this sourcefile. This is also the reason why he can't inline the call to alphaCentauri(). So, he emits a request in form of a "missing reference" into the object file:

sourcefile-a.obj
  Contains:      [main () : int] at 0x00  Missing References:      [alphaCentauri () : int] at 0x04  Machine Code:      0x12 0x01 0x02 0x03    // let's assume here is a call instruction      0x00 0x41 0xa2 0xb3      0x30 0x21 0x92 0xc3      0x03 0x31 0x02 0xf3


sourcefile-a.cpp
int alphaCentauri () {    return 0xBEEF;} 


sourcefile-b.obj
  Contains:      [alphaCentauri () : int] at 0x00  Missing References:  Machine Code:      0xa2 0xb3 0xc6 0x41       0x02 0xf3 0x03 0x31       0x30 0xc3 0xBE 0xEF     // heh, our "BEEF" :D      0x01 0x02 0x03 0x12 


Linking
The linker now has access to both object files and can do the final "Cleaning". He sees that in sourcefile-a.obj there's a missing reference to a function with the signature "alphaCentauri () : int", so he looks up whether one of the other object-files defines such function. If he can't find the definition, he emits something like

  undefined reference to alphaCentauri() in sourcefile-a.obj+0x04


. But as we where clever, we passed all necessary object files to it, and so the linker can finally replace the dummy value with the actual address of alphaCentauri(). It now also becomes evident that this is also the time to transform all relative addresses into absolute ones.

Our final binary then looks like:
      0x12 0x01 0x02 0x03          0x20 0x41 0xa2 0xb3 // 32 = 0x20      0x30 0x21 0x92 0xc3      0x03 0x31 0x02 0xf3      0xa2 0xb3 0xc6 0x41    // function "alphaCentauri()" starts here      0x02 0xf3 0x03 0x31       0x30 0xc3 0xBE 0xEF       0x01 0x02 0x03 0x12


Finally, some links to more information:



[Edited by - phresnel on April 23, 2009 7:36:30 AM]
Quote:Original post by phresnel
Into more detail about why partially initialized vtables can cause PITA

After thinking it through a bit more: you're right. I hadn't fully considered the case of multiple translation units, where the base class could be used in a different (and already compiled) source file. In this case, the compiler can only use the vtable to call into the class's functions, so there would certainly be problems if the vtable was only partially initialized.

Thanks for the in-depth explanation!
Quote:Original post by phresnel
Quote:Original post by Antheus
Quote:Original post by bpoint
All corner cases however are courtesy of C++'s arcane rules and general mess.

Which language do you know that also supports templates as a core language element but with less mess? To be honest, I only know that C++ has templates (and, if you want, Objective-C++).


Quote:Templates are compile-time polymorphism ...

Saying "Templates are compile-time polymorphism" is like saying "Cars are streets" or "Balls in the form of a truncated icosahedron are soccer". I don't really know what that phrase wants to express.

Quote:... and generally don't mix well with run-time polymorphism.

May I ask you to specify your intention a bit more?


The context of this thread and problem statement perhaps?

Specifically this:
Quote:Presently two extensions are planned. One will provide an insertion sort, placing the add()ed value into a proper position into the list.

The second (and more complicated) will provide a method to add values to the list


There is a container with lots of added functionality. Storage however may vary, yet only needs to perform rudimentary operation of add(), and consequently remove() and get(). To facilitate reuse, polymorphism of sorts must be employed.

First question that arises is whether allocator parameter as defined by std:: containers is sufficient to accomplish this.

If not, then same effect can be achieved without run-time polymorphism (avoiding it due to v-table problems) using templates only. Specifically, because of, again:
Quote:Presently two extensions are planned.
They are planned, foreseen and well defined, not YAGNI.

So instead of analyzing v-table madness into painstaking detail, why not first solve the problem in accordance with requirements the way std::stack solves it.
template < class T, class Container >
where Container provides the rudimentary functionality required by storage (add, remove, get).

The implementation of container can then be one of STL classes, or a custom one, defining only needed operations.
Quote:
Quote:
May I ask you to specify your intention a bit more?

The context of this thread and problem statement perhaps?


Not to sound rude or something, but that primary answer yields a semantic error in my language unit, though I can substitute what you mean [smile]
Thanks for the further workout.


Still, that does not answer my other question (not that you actually would have to answer, but you quoted them again, so it would be nice):

Quote:Original post by phresnel
Quote:Original post by Antheus
Quote:Original post by bpoint
All corner cases however are courtesy of C++'s arcane rules and general mess.

Which language do you know that also supports templates as a core language element but with less mess? To be honest, I only know that C++ has templates (and, if you want, Objective-C++).


Quote:Templates are compile-time polymorphism ...

Saying "Templates are compile-time polymorphism" is like saying "Cars are streets" or "Balls in the form of a truncated icosahedron are soccer". I don't really know what that phrase wants to express.

This topic is closed to new replies.

Advertisement