views:

140

answers:

6

A scene like this:
I've different of objects do the similar operation as respective func() implements.
There're 2 kinds of solution for func_manager() to call func() according to different objects

Solution 1: Use virtual function character specified in c++. func_manager works differently accroding to different object point pass in.

class Object{
  virtual void func() = 0;
}
class Object_A : public Object{
  void func() {};
}
class Object_B : public Object{
  void func() {};
}
void func_manager(Object* a)
{
   a->func();
}

Solution 2: Use plain switch/case. func_manager works differently accroding to different type pass in

typedef enum _type_t
{
  TYPE_A,
  TYPE_B
}type_t;

void func_by_a()
{
// do as func() in Object_A
}
void func_by_b()
{
// do as func() in Object_A
}
void func_manager(type_t type)
{
    switch(type){
        case TYPE_A:
            func_by_a();
            break;
        case TYPE_B:
            func_by_b();
        default:
            break;
    }
}

My Question are 2:
1. at the view point of DESIGN PATTERN, which one is better?
2. at the view point of RUNTIME EFFCIENCE, which one is better? Especailly as the kinds of Object increases, may be up to 10-15 total, which one's overhead oversteps the other? I don't know how switch/case implements innerly, just a bunch of if/else?

Thanks very much!

A: 

Using "dynamic dispatch" or "virtual dispatch" (i.e. invoking a virtual function) is better both with respect to design (keeping changes in one place, extensibility) and with respect to runtime efficiency (simple dereference vs. a simple dereference where a jump table is used or an if...else ladder which is really slow). As a slight aside, "dynamic binding" doesn't mean what you think... "dynamic binding" refers to resolving a variable's value based on its most recent declaration, as opposed to "static binding" or "lexical binding", which refers to resolving a variable by the current inner-most scope in which it is declared.

Design
If another programmer comes along who doesn't have access to your source code and wants to create an implementation of Object, then that programmer is stuck... the only way to extend functionality is by adding yet another case in a very long switch statement. Whereas with virtual functions, the programmer only needs to inherit from your interface and provide definitions for the virtual methods and, walla, it works. Also, those switch statements end up all over the place, and so adding new implementations almost always requires modifying many switch statements everywhere, while inheritance keeps the changes localized to one class.

Efficiency
Dynamic dispatch simply looks up a function in the object's virtual table and then jumps to that location. It is incredibly fast. If the switch statement uses a jump table, it will be roughly the same speed; however, if there are very few implementations, some programmer is going to be tempted to use an if...else ladder instead of a switch statement, which generally is not able to take advantage of jump tables and is, therefore, slower.

Michael Aaron Safyan
+2  A: 

The first solution is better if only because it's shorter in code. It is also easier to maintain and faster to compile: if you want to add types you need only add new types as headers and compilation units, with no need to change and recompile the code that is responsible for the type mapping. In fact, this code is generated by the compiler and is likely to be as efficient or more efficient than anything you can write on your own.

Virtual functions at the lowest level cost no more than an extra dereference in a table (array). But never mind that, this sort of performance nitpicking really doesn't matter at this microscopic numbers. Your code is simpler, and that's what matters. The runtime dispatch that C++ gives you is there for a reason. Use it.

wilhelmtell
+1  A: 
  1. I would say the first one is better. In solution 2 func_manager will have to know about all types and be updated everytime you add a new type. If you go with solution 1 you can later add a new type and func_manager will just work.

  2. In this simple case I would actually guess that solution 1 will be faster since it can directly look up the function address in the vtable. If you have 15 different types the switch statement will likely not end up as a jump table but basically as you say a huge if/else statement.

Andreas Brinck
A: 

From the design point of view the first one is definitely better as thats what inheritance was intended for, to make different objects behave homogeneously.

From the efficiency point of view in both alternatives you have more or less the same generated code, somewhere there must be the choice making code. Difference is that inheritance handles it for you automatically in the 1st one and you do it manually in the 2nd one.

Arkaitz Jimenez
+6  A: 

from the view point of DESIGN PATTERN, which one is better?

Using polymorphism (Solution 1) is better.
Just one data point: Imagine you have a huge system built around either of the two and then suddenly comes the requirement to add another type. With solution one, you add one derived class, make sure it's instantiated where required, and you're done. With solution 2 you have thousands of switch statements smeared all over the system and it is more or less impossible to guarantee you found all the places where you have to modify them for the new type.

from the view point of RUNTIME EFFCIENCE, which one is better? Especailly as the kinds of Object

That's hard to say.
I remember a footnote in Stanley Lippmann's Inside the C++ Object Model, where he says that studies have shown that virtual functions might have a small advantage against switches over types. I would be hard-pressed, however, to cite chapter and verse, and, IIRC, the advantage didn't seem big enough to make the decision dependent on it.

sbi
+1 for clarity, and i'll search that chapeter for some details again.
kingkai
+1 for perfectly capturing the chaos of the switch method.
Michael Aaron Safyan
@kingkai: I can't get at my book right now because it's at home and online I only found this http://www.amazon.com/dp/0201834545#reader_0201834545. I'd first look for that footnote at chapters 4.2 and 4.3.
sbi
@sbi, @Michael Aaron Safyan. About 'switch'-mess. Does not he encapsulates all that mess inside managing function. It seems not to be so terribly worse than OO encapsulation
Alexander Malakhov
<continued> and if he needs one more kind of behavior, he just changing single func, and he's done
Alexander Malakhov
@Alexander: Then you have a bunch of managing functions smeared out over the whole system. It's one of the strengths of OOP that it allows you to bundle the characteristics of a type into that type. Add to this that types might come in natural inheritance scenarios. That is, `A` and `B` share some characteristics, `B` and `C` share others, and yet others are shared by `A` and `C`. Such things can nicely be modeled using (multiple) inheritance to reduce redundancy. Try to capture that in a readable form in switches. Even with managing functions, this can very quickly turn into a nightmare.
sbi
@sbi: In general I agree with you. For me long swithes on functions looks really ugly (maybe because of little experience). I have only 1 consideration not to absolutely dismiss procedural solution: there is no silver bullet, and people tend to overuse any technique. For example, you can create separate module for manager functions and then it will not be that hard to properly handle new type. Anyway classes with single method looks not very OO, I think I would be using function objects (dont tell me they are also classes with single method :) ) and templates instead
Alexander Malakhov
@Alexander: Of course, absolutely dismissing any solution would be wrong. For example, (although it's very unlikely) you could be stuck with an architecture where indirect calls are expensive, but performance is a must. However, when you create a separate module with manager functions you're effectively programming OO (you can do this in C) - you're just not using your C++ compiler's proven and optimized implementation for it. There has to be a _very_ good and proven reason for this. In general, there is no such reason, which is why, in general, this is inferior.
sbi
+1  A: 

Why nobody suggests function objects? I think kingkai interested in solving the problem, not only that two solutions.
I'm not experienced with them, but they do their job:

struct Helloer{
    std::string operator() (void){
        return std::string("Hello world!");
    }
};

struct Byer{
    std::string operator() (void){
        return std::string("Good bye world!");
    }
};

template< class T >
void say( T speaker){
    std::cout << speaker() << std::endl;
}

int main()
{
   say( Helloer() );
   say( Byer() );
}

Edit: In my opinion this is more "right" approach, than classes with single method (which is not function call operator). Actually, I think this overloading was added to C++ to avoid such classes.
Also, function objects are more convenient to use, even if you dont want templates - just like usual functions.
In the end consider STL - it uses func objects everywhere and looks pretty natural. And I dont even mention Boost

Alexander Malakhov