Virtual Methods or Function Pointers

后端 未结 8 1445
春和景丽
春和景丽 2020-12-07 11:31

When implementing polymorphic behavior in C++ one can either use a pure virtual method or one can use function pointers (or functors). For example an asynchronous callback c

相关标签:
8条回答
  • 2020-12-07 12:16
    1. Change to pure virtual, first off. Then inline it. That should negate any method overhead call at all, so long as inlining doesn't fail (and it won't if you force it).
    2. May as well use C, because this is the only real useful major feature of C++ compared to C. You will always call method and it can't be inlined, so it will be less efficient.
    0 讨论(0)
  • 2020-12-07 12:20

    It's not clear from your example if you're creating a utility class or not. Is you Callback class intended to implement a closure or a more substantial object that you just didn't flesh out?

    The first form:

    • Is easier to read and understand,
    • Is far easier to extend: try adding methods pause, resume and stop.
    • Is better at handling encapsulation (presuming doGo is defined in the class).
    • Is probably a better abstraction, so easier to maintain.

    The second form:

    • Can be used with different methods for doGo, so it's more than just polymorphic.
    • Could allow (with additional methods) changing the doGo method at run-time, allowing the instances of the object to mutate their functionality after creation.

    Ultimately, IMO, the first form is better for all normal cases. The second has some interesting capabilities, though -- but not ones you'll need often.

    0 讨论(0)
  • 2020-12-07 12:20

    For example, let us look at an interface for adding read functionality to a class:

    struct Read_Via_Inheritance
    {
       virtual void  read_members(void) = 0;
    };
    

    Any time I want to add another source of reading, I have to inherit from the class and add a specific method:

    struct Read_Inherited_From_Cin
      : public Read_Via_Inheritance
    {
      void read_members(void)
      {
        cin >> member;
      }
    };
    

    If I want to read from a file, database, or USB, this requires 3 more separate classes. The combinations start to be come very ugly with multiple objects and multiple sources.

    If I use a functor, which happens to resemble the Visitor design pattern:

    struct Reader_Visitor_Interface
    {
      virtual void read(unsigned int& member) = 0;
      virtual void read(std::string& member) = 0;
    };
    
    struct Read_Client
    {
       void read_members(Reader_Interface & reader)
       {
         reader.read(x);
         reader.read(text);
         return;
       }
       unsigned int x;
       std::string& text;
    };
    

    With the above foundation, objects can read from different sources just by supplying different readers to the read_members method:

    struct Read_From_Cin
      : Reader_Visitor_Interface
    {
      void read(unsigned int& value)
      {
         cin>>value;
      }
      void read(std::string& value)
      {
         getline(cin, value);
      }
    };
    

    I don't have to change any of the object's code (a good thing because it is already working). I can also apply the reader to other objects.

    Generally, I use inheritance when I am performing generic programming. For example, if I have a Field class, then I can create Field_Boolean, Field_Text and Field_Integer. In can put pointers to their instances into a vector<Field *> and call it a record. The record can perform generic operations on the fields, and doesn't care or know what kind of a field is processed.

    0 讨论(0)
  • 2020-12-07 12:23

    One major advantage of the first method is it has more type safety. The second method uses a void * for iParam so the compiler will not be able to diagnose type problems.

    A minor advantage of the second method is that it would be less work to integrate with C. But if you're code base is only C++, this advantage is moot.

    0 讨论(0)
  • 2020-12-07 12:24

    The primary problem with Approach 2 is that it simply doesn't scale. Consider the equivalent for 100 functions:

    class MahClass {
        // 100 pointers of various types
    public:
        MahClass() { // set all 100 pointers }
        MahClass(const MahClass& other) {
            // copy all 100 function pointers
        }
    };
    

    The size of MahClass has ballooned, and the time to construct it has also significantly increased. Virtual functions, however, are O(1) increase in the size of the class and the time to construct it- not to mention that you, the user, must write all the callbacks for all the derived classes manually which adjust the pointer to become a pointer to derived, and must specify function pointer types and what a mess. Not to mention the idea that you might forget one, or set it to NULL or something equally stupid but totally going to happen because you're writing 30 classes this way and violating DRY like a parasitic wasp violates a caterpillar.

    Approach 3 is only usable when the desired callback is statically knowable.

    This leaves Approach 1 as the only usable approach when dynamic method invocation is required.

    0 讨论(0)
  • 2020-12-07 12:25

    Approach 1 (Virtual Function)

    • "+" The "correct way to do it in C++
    • "-" A new class must be created per callback
    • "-" Performance-wise an additional dereference through VF-Table compared to Function Pointer. Two indirect references compared to Functor solution.

    Approach 2 (Class with Function Pointer)

    • "+" Can wrap a C-style function for C++ Callback Class
    • "+" Callback function can be changed after callback object is created
    • "-" Requires an indirect call. May be slower than functor method for callbacks that can be statically computed at compile-time.

    Approach 3 (Class calling T functor)

    • "+" Possibly the fastest way to do it. No indirect call overhead and may be inlined completely.
    • "-" Requires an additional Functor class to be defined.
    • "-" Requires that callback is statically declared at compile-time.

    FWIW, Function Pointers are not the same as Functors. Functors (in C++) are classes that are used to provide a function call which is typically operator().

    Here is an example functor as well as a template function which utilizes a functor argument:

    class TFunctor
    {
    public:
        void operator()(const char *charstring)
        {
            printf(charstring);
        }
    };
    
    template<class T> void CallFunctor(T& functor_arg,const char *charstring)
    {
        functor_arg(charstring);
    };
    
    int main()
    {
        TFunctor foo;
        CallFunctor(foo,"hello world\n");
    }
    

    From a performance perspective, Virtual functions and Function Pointers both result in an indirect function call (i.e. through a register) although virtual functions require an additional load of the VFTABLE pointer prior to loading the function pointer. Using Functors (with a non-virtual call) as a callback are the highest performing method to use a parameter to template functions because they can be inlined and even if not inlined, do not generate an indirect call.

    0 讨论(0)
提交回复
热议问题