Alternative to c++ static virtual methods

前端 未结 12 2142
深忆病人
深忆病人 2020-12-04 21:58

In C++ is not possible to declare a static virtual function, neither cast a non-static function to a C style function pointer.

Now, I have a plain ol\' C SDK that us

相关标签:
12条回答
  • 2020-12-04 22:15

    You can make Base be a class template that takes its function pointers from its template argument:

    extern "C" {
    struct CStruct
    {
      void (*funA)(int, char const*);
      int (*funB)(void);
    };
    }
    
    template <typename T>
    class Base
    {
    public:
      CStruct myStruct;
      void FillPointers() {
        myStruct.funA = &T::myFunA;
        myStruct.funB = &T::myFunB;
      }
      Base() {
        FillPointers();
      }
    };
    

    Then, define your derived classes to descend from an instantiation of Base using each derived class as the template argument:

    class Derived1: public Base<Derived1>
    {
    public:
      static void myFunA(int, char const*) { }
      static int myFunB() { return 0; }
    };
    
    class Derived2: public Base<Derived2>
    {
    public:
      static void myFunA(int, char const*) { }
      static int myFunB() { return 1; }
    };
    
    int main() {
      Derived1 d1;
      d1.myStruct.funA(0, 0);
      d1.myStruct.funB();
      Derived2 d2;
      d2.myStruct.funA(0, 0);
      d2.myStruct.funB();
    }
    

    That technique is known as the curiously recurring template pattern. If you neglect to implement one of the functions in a derived class, or if you change the function signature, you'll get a compilation error, which is exactly what you'd expect to get if you neglected to implement one of the pure virtual functions from your original plan.

    The consequence of this technique, however, is that Derived1 and Derived2 do not have a common base class. The two instantiations of Base<> are not related in any way, as far as the type system is concerned. If you need them to be related, then you can introduce another class to serve as the base for the template, and then put the common things there:

    class RealBase
    {
    public:
      CStruct myStruct;
    };
    
    template <typename T>
    class Base: public RealBase
    {
      // ...
    };
    
    int main()
      RealBase* b;
      Derived1 d1;
      b = &d1;
      b->myStruct.funA(0, 0);
      b->myStruct.funB();
      Derived2 d2;
      b = &d2;
      b->myStruct.funA(0, 0);
      b->myStruct.funB();
    }
    

    Beware: Static member functions are not necessarily compatible with ordinary function pointers. In my experience, if the compiler accepts the assignment statements shown above, then you can at least be confident that they're compatible for that compiler. This code isn't portable, but if it works on all the platforms you need to support, then you might consider it "portable enough."

    0 讨论(0)
  • 2020-12-04 22:15

    Virtual functions are essentially function pointers under-the-hood. They just point to different functions for different classes. To simulate virtual-function behavior, have a function pointer stored somewhere, then to 'override' it just reassign it to some different function.

    Alternatively, you might want to test this, but I think interfaces have pretty good binary compatibility. You might get away with exposing a C++ interface composed entirely of pure virtual functions, so long as all the parameters and return types have a consistent binary format (eg. C types). It's not a standard, but it might be portable enough.

    0 讨论(0)
  • 2020-12-04 22:17

    You could just pass the functions directly into the base class constructor:

    class Base
    {
        Base()(int (*myFunA)(...), int (*myFunB)(...)) 
        { myStruct.funA = funA; myStruct.funB = myFunB; ...}
    private:
        CStruct myStruct;
    };
    
    class Derived1 : public Base
    {
        Derived1() : Base (myFunA, myFunB) {}
        static myFunA(...) {...};
        static myFunB(...) {...};
    };
    
    class Derived2 : public Base
    {
        Derived2() : Base (myFunA, myFunB) {}
        static myFunA(...) {...};
        static myFunB(...) {...};
    };
    
    int main()
    {
        Derived1 d1;
        Derived2 d2;
        // Now I have two objects with different functionality
    }
    
    0 讨论(0)
  • 2020-12-04 22:19

    I still can see a use for static virtual methods, here an example:

    class File
    {
        static virtual std::string extension()  {return "";}
    }
    
    class ExecutableFile : public File
    {
        // static because every executable has same extension
        static virtual std::string extension()  {return ".exe";}
    }
    
    
    std::string extension = "";
    
    // needing static
    extension = ExecutableFile::extension();
    
    // not needing static nor virtual
    ExecutableFile exeFile;
    extension = exeFile.extension();
    
    // needing virtual
    File* pFile = &exeFile;
    extension = pFile->extension();
    
    0 讨论(0)
  • 2020-12-04 22:19

    These things would certainly be useful- namely, to force all objects in a class hierarchy to expose a factory method instead of an ordinary constructor. Factories are very useful for ensuring you never build invalid objects, a design guarantee that you cannot enforce nearly as well with ordinary constructors.

    To build 'virtual statics' requires building your own "static v-table" by hand into all the objects that need it. Ordinary virtual member functions work because the compiler builds a secret table of function pointers called the VTABLE into all instances of your class. When you build a "T" object, the function pointers in this table are assigned to the addresses of 1st ancestor providing that API. Overriding a function then simply becomes replacing the original pointer in the object you get from 'new' with the new one provided in the derived class. Of course, the compiler and runtime handle this all for us.

    But, back in the really old days before modern c++ (so I'm told), you had to set this magic up yourself. And that's still the case for virtual statics. The good news is this- the vtable you build by hand for them is actually simpler than the 'ordinary' one, its entries are no more expensive in any way-including space & performance- than those for member functions. Just define the base class with an EXPLICIT set of function pointers (the static vtable) for the APIs you want supported:

    template<typename T>
    class VirtualStaticVtable {
    private:
       typedef T (*StaticFactory)(KnownInputParameters params);
    
       StaticFactory factoryAPI;  // The 1 and only entry in my static v-table
    
    protected:
       VirtualStaticVtable(StaticFactory factoryApi) : factoryAPI(factoryApi) {}
       virtual ~VirtualStaticVtable() {}
    };
    

    Now, every object that should support a static factory method can be derived from this class. They quietly pass in their own factory to their constructor, and it only adds 1 pointer to the resulting objects' sizes (just like an ordinary VTable entry).

    Strousup and co. could still add this idiomatic pattern to the core language if they wanted to. It wouldn't even be that hard. Every object in such a "C+++" would simply have 2 vtables instead of 1- 1 for member functions taking 'this' as an argument and 1 for ordinary function pointers. Until that day, however, we're stuck with manual vtables just like the old C-programmers were in the days before c++.

    0 讨论(0)
  • 2020-12-04 22:24

    I think you just need to use a plain virtual function. A static virtual function does not make sense, because a virtual function is resolved at runtime. What's there to resolve when the compiler knows exactly what the static function is?

    In any case, I would suggest leaving the existing function pointer solution in place if possible. Baring that, consider using a normal virtual function.

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