Which causes more code-bloat: inline virtual or template class?

R

RainBow

Greetings!!

I introduced the so-called "thin-template" pattern for controlling the
code bloat caused due to template usage.

However, one of the functions in the template happens to be virtual as
well. To support thin-template, I need to make virtual function as
inline.

Now, I know that compiler would generate an out-of-line copy when it
takes the address of my inline virtual fucntion, thus making _two_
copies of this function : one out-of-line to be used by VTABLE, and one
inline for genuine inline substituions.

Doubts:

1. Is declaring a virtual function as inline a good design? Does it
indicate their is some flaw in my design? I have not seen much code
that makes a virtual function as inline too, and hence this doubt.

2. As a rule of thumb, who should cause more code bloat : normal
template or out-of-line function? I wanna know this /cuz if template
cuase more code bloat, I wanna keep my virtual as inline. ELse I remove
thin template and make my virtual as non-inline.


The below code should give some idea as to what I am doing:

class Base
{
public:
virtual int DoSomething() = 0;
protected:
Base();
};

class IntermediateBase : public Base
{
protected:
IntermediateBase(void* aSomeParam, void* aArg) :
iSomeParam(aSomeParam), iArgs(aArg) {}
virtual int DoSomething() = 0;
protected:
void* iSomeParam;
void* iArgs;
};


template <class TYPE, class INPUT>
class ConcreteClass : public IntermediateBase
{
typedef int (TYPE::*MemberFuncPtr)(const INPUT&);
public:
inline ConcreteClass(TYPE& aCommandType, INPUT& aArgumentsToCommand,
MemberFuncPtr aMFP)
: IntermediateBase( static_cast<TYPE*>(&aCommandType),
static_cast<INPUT*>(&aArgumentsToCommand) ),
iMFP(aMFP)
{
}

inline virtual int DoSomething() // VIRTUAL AND LINE - THIS CODE
SMELLS ROT?
{
return ( static_cast<TYPE*>(iSomeParam)->*ConcreteClass::iMFP )(
*(static_cast<INPUT*>(iArgs)) );
}
private:
MemberFuncPtr iMFP;
};
 
V

Victor Bazarov

RainBow said:
I introduced the so-called "thin-template" pattern for controlling the
code bloat caused due to template usage.

Could you maybe share your findings how much bloat you manage to control
and what problems eliminating the bloat helps to avoid? Thanks!

V
 
E

E. Robert Tisdale

RainBow said:
I introduced the so-called "thin-template" pattern
for controlling the code bloat caused due to template usage.

However,
one of the functions in the template happens to be virtual as well.
To support thin-template, I need to make virtual function as inline.

Now, I know that compiler would generate an out-of-line copy
when it takes the address of my inline virtual fucntion,
thus making _two_ copies of this function:
one out-of-line to be used by VTABLE and
one inline for genuine inline substituions.

Doubts:

1. Is declaring a virtual function as inline a good design?
Yes.

Does it indicate [that there] is some flaw in my design?
I have not seen much code that makes a virtual function as inline too
and hence this doubt.

This is improbable.
If you apply a virtual function to an object
in the scope where it was created,
the compiler knows the actual type of the object
and a good optimizing compiler will inline the virtual function.
2. As a rule of thumb, who should cause more code bloat:
normal template or out-of-line function?
I wanna know this because, if template cuase more code bloat,
I wanna keep my virtual as inline. Otherwise,
I remove thin template and make my virtual as non-inline.

The below code should give some idea as to what I am doing:

class Base {
protected:
Base(void);
public:
virtual int DoSomething(void) = 0;
};

class IntermediateBase: public Base {
protected:
void* iSomeParam;
void* iArgs;
IntermediateBase(void* aSomeParam, void* aArg):
iSomeParam(aSomeParam), iArgs(aArg) { }
virtual
int DoSomething(void) = 0;
};

template <class TYPE, class INPUT>
class ConcreteClass: public IntermediateBase {
private:
typedef int (TYPE::*MemberFuncPtr)(const INPUT&);
MemberFuncPtr iMFP;
public:
ConcreteClass(TYPE& aCommandType,
INPUT& aArgumentsToCommand, MemberFuncPtr aMFP):
IntermediateBase(static_cast<TYPE*>(&aCommandType),
static_cast<INPUT*>(&aArgumentsToCommand)), iMFP(aMFP) { }

virtual
int DoSomething(void) { // VIRTUAL AND LINE - THIS CODE SMELLS ROT?
return (static_cast<TYPE*>(iSomeParam)->*ConcreteClass::iMFP)
(*(static_cast<INPUT*>(iArgs)));
}
};

inline functions do *not* cause code bloat.
They exist simply
to discourage programmers who are concerned about performance
from inlining code manually.

I *always* define inline functions (and operators)
and I let the optimizing compiler decide
whether to actually inline them or not.

inline functions can increase compile time substantially.
You might consider taking advantage of both
inline *and* external function definitions:
> cat file.h
#ifndef GUARD_FILE_H
#define GUARD_FILE_H 1

#ifdef EFINE_INLINE
inline
double f(double x) {
return x*(x + 2.0) + 1.0;
}
#else //EFINE_INLINE
double f(double x);
#endif//EFINE_INLINE
#endif//GUARD_FILE_H
> cat file.cc
#undef EFINE_INLINE
#include "file.h"

double f(double x) {
return x*(x + 2.0) + 1.0;
}
> g++ -DEFINE_INLINE -Wall -ansi -pedantic -O3 -c file.cc
> nm --demangle file.o
00000000 T f(double)

This allows your inline and external function definitions
to coexist peacefully.
Use the -DEFINE_INLINE option only after you have finished
testing and debugging all of your code.
This will speed up the program development cycle
and allow you to optimize your code just before deployment.
 
R

RainBow

Please note: when I say "I introduced" - I did not mean I invented
it/discovered it. I learnt this pattern when working on Symbian. If you
google for "thin template", one of the first 5 search results should
take you to Symbian site.

Also, I am sorry, but I am _not_ doing this r&d currently - I just want
a tentative answer for this. I know that the answer depends how often
are you calling the Execute() call from what all compilation units -
and I forgot to mention that in my earlier question - its just that
this inline function will be called from atmost 2 or 3 compilation
units. So is the case with template instantiation (okay, its
obvious...). :)

I will surely post my results in this group as and when I do that
activity - but right now, I just want a rough, tentative answer so that
I can finalise my design and proceed.

Thanks,
-Viren
 
G

Greg

RainBow said:
Greetings!!

I introduced the so-called "thin-template" pattern for controlling the
code bloat caused due to template usage.

However, one of the functions in the template happens to be virtual as
well. To support thin-template, I need to make virtual function as
inline.

Now, I know that compiler would generate an out-of-line copy when it
takes the address of my inline virtual fucntion, thus making _two_
copies of this function : one out-of-line to be used by VTABLE, and one
inline for genuine inline substituions.

Doubts:

1. Is declaring a virtual function as inline a good design? Does it
indicate their is some flaw in my design? I have not seen much code
that makes a virtual function as inline too, and hence this doubt.

I wouldn't say that declaring a function inline is part of a "design"
really - I suppose it could be called a "practice". And one that is
unlikely to make much of a difference to a virtual class method. To
inline a virtual function call, the exact type of the object involved
in the call must be unvarying and be known at compile time. Needless to
say, there are few occasions where a polymorphic reference can be only
one type, and the compiler can tell what its type must be. After all,
the whole point of polymorphism is that an object's type is determined
at runtime, based on the operating state of the program.
2. As a rule of thumb, who should cause more code bloat : normal
template or out-of-line function? I wanna know this /cuz if template
cuase more code bloat, I wanna keep my virtual as inline. ELse I remove
thin template and make my virtual as non-inline.


The below code should give some idea as to what I am doing:

class Base
{
public:
virtual int DoSomething() = 0;
protected:
Base();
};

class IntermediateBase : public Base
{
protected:
IntermediateBase(void* aSomeParam, void* aArg) :
iSomeParam(aSomeParam), iArgs(aArg) {}
virtual int DoSomething() = 0;
protected:
void* iSomeParam;
void* iArgs;
};


template <class TYPE, class INPUT>
class ConcreteClass : public IntermediateBase
{
typedef int (TYPE::*MemberFuncPtr)(const INPUT&);
public:
inline ConcreteClass(TYPE& aCommandType, INPUT& aArgumentsToCommand,
MemberFuncPtr aMFP)
: IntermediateBase( static_cast<TYPE*>(&aCommandType),
static_cast<INPUT*>(&aArgumentsToCommand) ),
iMFP(aMFP)
{
}

inline virtual int DoSomething() // VIRTUAL AND LINE - THIS CODE
SMELLS ROT?
{
return ( static_cast<TYPE*>(iSomeParam)->*ConcreteClass::iMFP )(
*(static_cast<INPUT*>(iArgs)) );
}
private:
MemberFuncPtr iMFP;
};

Template code "bloat" is measured not by the total amount of code
generated from instantiating templates (which can be considerable) but
rather by the amount of duplicated code from instantiating templates
with interchangeable types (from the template's perspective). For
instance if a class template accepts a pointer type as its type
parameter, but does nothing special depending on the type being pointed
to, then instantiating the template with a series of different pointer
types will "bloat" the program with essentially duplicated code. Some
compilers can detect duplicated code and "fold" the code into a single
set of instructions (leaving stubs for the "folded" routines since
template functions must each retain a unique address). Of course if the
compiler folds duplicate code then there is less incentive for the
programmer to avoid template bloat at the source code level.

In the code above, there seems to be little opportunity for template
bloat, since the class template seems quite type-specific. Furthermore
any optimization seems premature at this point, particularly if it
complicates the implementation.

I would instead write the program using the best design available and
then test the program first for correctness, and then for performance.
If performance (measured by the speed or the size of the program) turns
out to be inadequate at that point, then it is a good idea to start
looking for optimizations. The advantage of this approach is in that
case that the program turns out to be small and fast enough using the
optimal design, then no time would have been wasted making unneeded
optimizations that would have comprised the design. And in the case
that the program turns out not to b small or fast enough, then any
optimizations that are made start with a working program - meaning that
any bugs caused by subsequent changes will be much easier to track
down.

Greg
 
R

RainBow

Thank you very much Greg for that beautiful information. Surely, I am
thinking about optimisation even before a single line of code is
written - while we are still in the design phase. I think I have been
too much scared of code size (at binary level of course) of a C++
program versus a C program. In the past, I was criticised for writing
too many classes when perhaps a few C functions would have done the
trick - keeping the program size extremely small (albeit at the cost of
increasing the maintenance efforts).

This time, I did not want to take any chances and be careful about the
estiamted program size right from beginning and hence these (silly??)
concerns. :) But your posting has opened my eyes and I realise what a
bad approach I had been taking.

Thank you very much again.

Have a great day ahead!!

--V
 
G

Greg

RainBow said:
Thank you very much Greg for that beautiful information. Surely, I am
thinking about optimisation even before a single line of code is
written - while we are still in the design phase. I think I have been
too much scared of code size (at binary level of course) of a C++
program versus a C program. In the past, I was criticised for writing
too many classes when perhaps a few C functions would have done the
trick - keeping the program size extremely small (albeit at the cost of
increasing the maintenance efforts).

This time, I did not want to take any chances and be careful about the
estiamted program size right from beginning and hence these (silly??)
concerns. :) But your posting has opened my eyes and I realise what a
bad approach I had been taking.

Thank you very much again.

Have a great day ahead!!

--V

Presumably you could have the linker generate a link map for the
program you want to make smaller. A link map would tell you how how
many functions were compiled into the binary and the size of each one.
Then you would only have to work to make the functions either smaller
or fewer or both.

I think that would be a more effective approach than asking the readers
of this newgroup which functions in your program to target.

Greg
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top