views:

71

answers:

4

I have a set of classes which are all derived from a common base class. I want to use these classes polymorphically. The interface defines a set of getter methods whose return values are constant across a given derived class, but vary from one derived class to another. e.g.:

enum AVal
{
  A_VAL_ONE,
  A_VAL_TWO,
  A_VAL_THREE
};

enum BVal
{
  B_VAL_ONE,
  B_VAL_TWO,
  B_VAL_THREE
};

class Base
{
  //...
  virtual AVal getAVal() const = 0;
  virtual BVal getBVal() const = 0;
  //...
};

class One : public Base
{
  //...
  AVal getAVal() const { return A_VAL_ONE };
  BVal getBVal() const { return B_VAL_ONE };
  //...
};

class Two : public Base
{
  //...
  AVal getAVal() const { return A_VAL_TWO };
  BVal getBVal() const { return B_VAL_TWO };
  //...
};

etc.

Is this a common way of doing things? If performance is an important consideration, would I be better off pulling the attributes out into an external structure, e.g.:

struct Vals
{
  AVal a_val;
  VBal b_val;
};

storing a Vals* in each instance, and rewriting Base as follows?

class Base
{
  //...
  public:
    AVal getAVal() const { return _vals->a_val; };
    BVal getBVal() const { return _vals->b_val; };
  //...
  private:
    Vals* _vals;
};

Is the extra dereference essentially the same as the vtable lookup? What is the established idiom for this type of situation? Are both of these solutions dumb? Any insights are greatly appreciated

+1  A: 

The first method seems clearer and forces you to override those methods (at the first child anyway). I think the overhead of virtual calls tends to be less than one might expect. Only if you profile the code and the virtual calls are taking a ton of time would I attempt to do optimizations as in your second approach.

That being said, what problem are you trying to solve? Sometimes class ids like this are useful but sometimes a different interface abstraction can accomplish the same thing without having have such an interface at all.

Mark B
Well, I am writing a virtual machine for a scripting language I am putting together. **DISCLAIMER:** *This project is purely for fun/exploration/educational purposes.* The actual class hierarchy is the set of script-type primitives/container classes. The attributes being accessed consist of various "type" information (hence constant to a class, variable over the set of classes). Because shuffling these objects around and executing these getters is a big part of what the core of the vm does, I wanted to avoid doing something that was hoaky or obviously inefficient.
acanaday
+1  A: 

I personally would implement a sort of, GetTypeStats(), which returns a (reference to) struct which contains all of the derived specific information, or a QueryInterface like you would find in D3D. You should also consider static polymorphism in this scenario. However, if your classes MUST be runtime polymorphic, then there is nothing you can really do to eliminate the virtual function calls.

DeadMG
Oh yes, thanks for bringing this up! The current implementation actually does use the external structure method mentioned above and provides a getter method to get const access to the structure directly.
acanaday
You could create the struct in the base class, and let the derived class set the data members in the constructor. That would remove the vfcalls.
DeadMG
The problem with that approach (creating the struct in the base class), if I am not mistaken, is that each instance will then contain a copy of the structure. Using the virtual method or the pointer to external struct, many instances can share one set of type information. Did I misunderstand you?
acanaday
Managing those pointers is going to cost much more than just duping the struct, especially if it only contains a couple of enum values. Dereferencing it, compared to a constant offset access, plus the thread-safe smart-pointer overheads, is not a good idea for such small data.
DeadMG
The current implementation (please don't beat me up) doesn't use smart pointers to dynamic memory. Rather, one `extern const` instance of each struct is declared in the header file for each derived class and defined in the corresponding source file. The address of these structs are passed to the base class in the derived constructors where it is assigned to a `const struct*` in the base class. Is this terribly ugly?
acanaday
@acanaday: Yes, yes it is.
DeadMG
I stole this scheme from python which has statically initialized global type objects to which all instances of a given type have a pointer. It may be the case that this is simply more appropriate to do in C vs C++. @DeadMG: Ugliness being given, is this also problematic? i.e. Is there another way to allow the instances to all share a reference to statically allocated type data that isn't so ugly? An established pattern, maybe?
acanaday
@acanaday: The thing is that you're not actually saving yourself substantial time or memory by using statically allocated data. Stack-based data isn't any faster than heap (can even be substantially slower) if it's a long way back from the stack top and it would be faster and cheaper to use the original virtual function method, as well as easier. Ultimately, I feel that you've spent a lot of time on what is almost certainly a trivial time/memory concern. Did you profile and determine that this is problematic?The best way of doing this is to have the data in the base class.
DeadMG
Aha, I hadn't even considered the performance costs of the statically initialzed type data. No, I haven't profiled yet. This really is a case of premature optimization. I had been using the virtual methods and it occurred to me that since these getters were executed in the core of the code, I might be better off using non-virtual methods and a structure scheme similar to the python VM, so I thought I'd survey the stackers. I'll probably go back to the virtuals and compare the actual performance difference when the system is more mature. I really, *really* appreciate all the feedback though.
acanaday
@acanaday: No problem. But, always profile before optimizing.
DeadMG
A: 

If all that differs are the values and they are fixed at compile-time, you could make them template arguments:

template< AVal aval, BVal bval>
class Derived : public Base
{
  AVal getAVal() const { return aval };
  BVal getBVal() const { return bval };
};

typedef Derived<A_VAL_ONE, B_VAL_ONE> One;
typedef Derived<A_VAL_TWO, B_VAL_TWO> Two;
sbi
This, however, doesn't circumvent the need for virtual and is less readable (IMHO) than the explicit virtual overrides in the first example.
acanaday
@acanaday: I'm guessing that sbi's post, like mine, was just trying to give you some additional insight. Don't be hatin', we're just trying to help! :)
John Dibling
Oh, sorry! I appreciate the input either way!
acanaday
A: 

This is a common way of doing a poor man's dynamic_cast for polymorphic types, when the programmer wants to avoid the use of dynamic_cast. In those cases, this is a micro-optimization. As with all micro-optimizations, you need to use sound judgement based on need before going ahead with it. If the profiler tells you there is no performance gain in doing this rather than dynamic_cast, you would probably be much better off just using dynamic_cast.

John Dibling
I am not disagreeing with you, but I'm not sure how `dynamic_cast` is an *alternative* to what I've presented here. I might be, for example running a list of these objects through a function whose behavior switches depending on the value returned from one of these functions. Are you suggesting I try to use `dynamic_cast` to cast to the derived types in sequence until I hit upon a cast that doesn't return `NULL` instead of switching on the getter value? (Have I vastly misunderstood your suggestion?)
acanaday
I should also mention that determining the appropriate cast from base to derived in not necessarily the goal here. In fact, sometimes no casting is desired. Am I still misunderstanding?
acanaday
@acanaday: I'm not saying that it is. All I'm saying is that a lot of the times I see a construct like this, it is an attempt at a poor-man's `dynamic_cast`. My post may or may not apply to you.
John Dibling
Oh, ok! Thank you for your insight! I just wanted to make sure I wasn't missing something.
acanaday