Reducing the virtual function table size...

C

Chris Thomasson

Consider an an object that that can has 7 or 8 functions. If you create an
abstract base class for the "interface" of the object, well, that means 7 or
8 pure virtual functions right? Well, IMHO, that's way too much... I was
wondering if the following technique is "frowned" upon:


<pseudo-code>
_____________
typedef struct ... vztimespec_t;

class condmutex_base {
public:
enum wait_e {
WAITLOCK, TRYLOCK, WAITCOND
};

enum wake_e {
UNLOCK, SIGNAL, BROADCAST
};

protected:
condmutex_base() {}

public:
virtual ~condmutex_base() {}

private:
virtual bool wait(wait_e, vztimespec_t const*) = 0;
virtual bool wake(wake_e) = 0;

public:
inline void unlock() { wake(wake_e::UNLOCK); }
inline void waitlock() { wait(wait_e::WAITLOCK, 0); }
inline void waitcond() { wait(wait_e::WAITCOND, 0); }
inline bool trylock() { return wait(wait_e::TRYLOCK, 0); }
inline bool signal() { return wake(wake_e::SIGNAL); }
inline bool broadcast() { return wake(wake_e::BROADCAST); }

inline bool timedwaitcond(vztimespec_t const *tspec) {
return wait(wait_e::WAITCOND, tspec);
}
};
_____________




I only have 2 virtual functions now, not counting dtor, ect... Well, 2 is
better than 7, or 8?


Any thoughts?

:^)
 
I

Ian Collins

Chris said:
Consider an an object that that can has 7 or 8 functions. If you create
an abstract base class for the "interface" of the object, well, that
means 7 or 8 pure virtual functions right? Well, IMHO, that's way too
much...

Why, is it causing you problems?
I was wondering if the following technique is "frowned" upon:
All you are doing is adding an extra layer of indirection in dispatch,
what do you hope to gain from this?
<pseudo-code>
_____________
typedef struct ... vztimespec_t;

class condmutex_base {
public:
enum wait_e {
WAITLOCK, TRYLOCK, WAITCOND
};

enum wake_e {
UNLOCK, SIGNAL, BROADCAST
};

protected:
condmutex_base() {}

public:
virtual ~condmutex_base() {}

private:
virtual bool wait(wait_e, vztimespec_t const*) = 0;
virtual bool wake(wake_e) = 0;

public:
inline void unlock() { wake(wake_e::UNLOCK); }

drop the inline.
 
C

Chris Thomasson

Ian Collins said:
Why, is it causing you problems?

No problems. Its a personal thing I guess.. ;^)

I see no need for 7 to 8 function pointers when all of its functionality can
be amortized into 2 or 3 low-level pure virtual functions... You can add
extra "functionality" without increasing the virtual table size. I am
willing to take the hit in the actual ::wake(...) or ::wait(...) functions
wrt branching on the enum value.

All you are doing is adding an extra layer of indirection in dispatch,
what do you hope to gain from this?

Amortized virtual function table size... IMHO, you don't want to pass around
"interface" objects that have tons of function pointers... Try to condense 7
to 8 functions of the object as a whole, into 2 or 3 low-level pure virtual
functions...


[...]
 
T

tragomaskhalos

I see no need for 7 to 8 function pointers when all of its functionality can
be amortized into 2 or 3 low-level pure virtual functions... You can add
extra "functionality" without increasing the virtual table size. I am
willing to take the hit in the actual ::wake(...) or ::wait(...) functions
wrt branching on the enum value.

Amortized virtual function table size... IMHO, you don't want to pass around
"interface" objects that have tons of function pointers... Try to condense 7
to 8 functions of the object as a whole, into 2 or 3 low-level pure virtual
functions...

(Enable usual caveats about implementations not necessarily using
vtables and 4-byte pointers ...)
Well there's premature optimisation, and then there's just madness.
Firstly, you do know that the "7 or 8 function pointers" exist once
per class, not per instance, right ?
Secondly, why do you think that passing around "interface objects that
have tons of function pointers" is a problem? You do know that there
is no copying of vtables going on, right ?
So your problem comes down to "I don't like the idea of a single block
of 32 bytes per condmutex-derived class in my program, I really want
to push that down to 8 bytes". I think we can all agree that, unless
you have a vast number of subclasses and are in a very contrained
memory environment, this is a bit silly.
 
P

peter koch

Consider an an object that that can has 7 or 8 functions. If you create an
abstract base class for the "interface" of the object, well, that means 7 or
8 pure virtual functions right?
I dont see why. As others have explained, the vtable is a per class
entity: the instance just has a pointer to the vtable.
Well, IMHO, that's way too much... I was
wondering if the following technique is "frowned" upon:
Not at all. Actually, I'd often prefer virtual to be private.
<pseudo-code>
_____________
typedef struct ... vztimespec_t;

class condmutex_base {
public:
enum wait_e {
WAITLOCK, TRYLOCK, WAITCOND
};

enum wake_e {
UNLOCK, SIGNAL, BROADCAST
};

protected:
condmutex_base() {}

public:
virtual ~condmutex_base() {}

private:
virtual bool wait(wait_e, vztimespec_t const*) = 0;
virtual bool wake(wake_e) = 0;

public:
inline void unlock() { wake(wake_e::UNLOCK); }
inline void waitlock() { wait(wait_e::WAITLOCK, 0); }
inline void waitcond() { wait(wait_e::WAITCOND, 0); }
inline bool trylock() { return wait(wait_e::TRYLOCK, 0); }
inline bool signal() { return wake(wake_e::SIGNAL); }
inline bool broadcast() { return wake(wake_e::BROADCAST); }

inline bool timedwaitcond(vztimespec_t const *tspec) {
return wait(wait_e::WAITCOND, tspec);
}};

_____________

I only have 2 virtual functions now, not counting dtor, ect... Well, 2 is
better than 7, or 8?
It is better in the sense, that in reality only two virtual functions
are needed. The extra storage cost of the extra virtual functions
should not matter in anything but the most extreme cases.

/Peter
 
M

marcus hall

I dont see why. As others have explained, the vtable is a per class
entity: the instance just has a pointer to the vtable.
Not at all. Actually, I'd often prefer virtual to be private.

It is better in the sense, that in reality only two virtual functions
are needed. The extra storage cost of the extra virtual functions
should not matter in anything but the most extreme cases.

Yes, you do save a few pointers in the vtable, but now each implementation
of wait() and wake() has to separate out the different cases at runtime,
which will likely be done with a big switch() which is implemented by a
jump table, so you are back to the same or greater cost, plus additional
run-time overhead.

Where this might be a win, though, is if there is a significant amount of
common code in wait() and wake() for all cases. Then in this implementation,
that code would exist only once, but would be replicated in each of the
other cases (or more likely pulled out into a common subroutine or two).

Now, consider if you have a derived class that wants to change the behavior
of the signal() method. In this case, the entire wake() method would
have to be re-implemented. With individual functions, only signal()
would need re-implementation. Obviously you expect derived classes,
otherwise what is the point in having virtual functions at all??
 
C

Chris Thomasson

marcus hall said:
Now, consider if you have a derived class that wants to change the
behavior
of the signal() method. In this case, the entire wake() method would
have to be re-implemented. With individual functions, only signal()
would need re-implementation. Obviously you expect derived classes,
otherwise what is the point in having virtual functions at all??

Why couldn't a derived class just do something like:



class baseclass : public condmutex_base {
// ...

protected:
virtual bool wake(condmutex_base::wake_e flag) {
// ... switch on flag
return whatever;
}

// ...
};


class derived : public baseclass
// ...

private:
bool wake(condmutex_base::wake_e flag) {

// check for signals; we only want to change signals!
if (flag == condmutex_base::SIGNAL) {
// custom stuff here...
return whatever;
}

// call the base class of derived.
return ((baseclass*)this))->wake(flag);
}

// ...
};


I don't see why a derived class would have to implement the "entire" wake
function... If it wants to change the behavior of signal, it test the enum
flag value against signal, and if it matches, then it executes the custom
signal and returns to the caller... Otherwise, it passes control to the
baseclass by invoking its wake function and passing it the enum flag..


Any thoughts?
 
I

Ian Collins

Chris said:
[...]

Now, consider if you have a derived class that wants to change the
behavior
of the signal() method. In this case, the entire wake() method would
have to be re-implemented. With individual functions, only signal()
would need re-implementation. Obviously you expect derived classes,
otherwise what is the point in having virtual functions at all??


Why couldn't a derived class just do something like:



class baseclass : public condmutex_base {
// ...

protected:
virtual bool wake(condmutex_base::wake_e flag) {
// ... switch on flag
return whatever;
}

// ...
};


class derived : public baseclass
// ...

private:
bool wake(condmutex_base::wake_e flag) {

// check for signals; we only want to change signals!
if (flag == condmutex_base::SIGNAL) {
// custom stuff here...
return whatever;
}

// call the base class of derived.
return ((baseclass*)this))->wake(flag);
}

// ...
};


I don't see why a derived class would have to implement the "entire" wake
function... If it wants to change the behavior of signal, it test the enum
flag value against signal, and if it matches, then it executes the custom
signal and returns to the caller... Otherwise, it passes control to the
baseclass by invoking its wake function and passing it the enum flag..


Any thoughts?
A lot of effort when just using a set of virtual functions would do the
same thing in a much more obvious (and probably efficient) way.
 
C

Chris Thomasson

Ian Collins said:
Chris said:
[...]
Now, consider if you have a derived class that wants to change the
behavior
[...]
I don't see why a derived class would have to implement the "entire" wake
function... [...]

Any thoughts?
A lot of effort when just using a set of virtual functions would do the
same thing in a much more obvious (and probably efficient) way.

Okay. I was trying to figure out how many virtual functions are generally
acceptable for an object that a user "usually" creates large numbers of...
Sounds like using 7-8 functions instead of 2-3 is a fairly acceptable
practice. Well, that does make it easier and less error-prone on my end as a
library implementer. Thank you all for your input.

:^)
 
I

Ian Collins

Chris said:
Okay. I was trying to figure out how many virtual functions are
generally acceptable for an object that a user "usually" creates large
numbers of... Sounds like using 7-8 functions instead of 2-3 is a fairly
acceptable practice. Well, that does make it easier and less error-prone
on my end as a library implementer. Thank you all for your input.
I think you are missing one very important fact - each *class* with
virtual functions has a virtual function table (vtable). Each
*instance* of the class has a pointer to *the* vtable for that class.
So the number of virtual functions is irrelevant in the context of an
object that a user "usually" creates large numbers of.

Try this:

#include <iostream>

struct X {
virtual void f() {}
virtual void g() {}
};

int main() {
std::cout << sizeof(X) << std::endl;
return 0;
}
 
J

Jerry Coffin

[ ... ]
Okay. I was trying to figure out how many virtual functions are generally
acceptable for an object that a user "usually" creates large numbers of...
Sounds like using 7-8 functions instead of 2-3 is a fairly acceptable
practice. Well, that does make it easier and less error-prone on my end as a
library implementer. Thank you all for your input.

The number of instances of the class is irrelevant, because (at least as
normally implemented) each object only contains a pointer to the vtable
for the class.

The relevant question is how many other classes derive from this one,
and (in particular) whether most of the virtual functions are likely to
be (nearly) meaningless for many of those derived classes. The vtable
for each derived class is a superset of the vtable for its base, so
forcing an extremely large vtable on an extremely large number of
derived classes can become a problem. Problems stemming from this source
seem to be fairly unusual.
 

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

Staff online

Members online

Forum statistics

Threads
474,296
Messages
2,571,535
Members
48,279
Latest member
RedaBruno6

Latest Threads

Top