RainBow wrote:
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