Search code examples
c++classfunction-pointersassignment-operatorskia

Why create a struct of function pointers inside a class?


I was digging around in the Vulkan backend for the Skia graphics API, found here, and I don't understand a piece of code.

Here's the smallest code example:

struct VulkanInterface : public SkRefCnt {

public:
    VulkanInterface(VulkanGetProc getProc,
                    VkInstance instance,
                    VkDevice device,
                    uint32_t instanceVersion,
                    uint32_t physicalDeviceVersion,
                    const VulkanExtensions*);

    /**
     * The function pointers are in a struct so that we can have a compiler generated assignment
     * operator.
     */
    struct Functions {
        VkPtr<PFN_vkCreateInstance> fCreateInstance;
        VkPtr<PFN_vkDestroyInstance> fDestroyInstance;

        // a ton more functions here
    } fFunctions;
};

Why would you create a struct of function pointers in a class?

Why this extra layer of abstraction where you have to add fFunctions-> everywhere?

I know there's a comment with an explanation and I know what those words mean, but I don't understand the comment as a whole. I just need it broken down a little more. Thanks.


Solution

  • With regular polymorphic inheritance

    struct Base
    {
        virtual ~Base() = default;
        virtual void foo();
        // ...
    };
    
    struct D1 : Base
    {
        void foo() override;
        // ...
    };
    
    struct D2 : Base
    {
        void foo() override;
        // ...
    };
    

    You cannot assign to base class without slicing:

    D1 d1;
    Base b = d1;
    b.foo(); // call Base::Foo
    

    or treat object with value semantic:

    D1 d1;
    D2 d2;
    d2 = d1; // Illegal
    

    you have to use (smart) pointer instead.

    In addition, you cannot mix (at runtime) from different virtual functions

    Base base;
    base.foo = &D2::foo; // imaginary syntax, Illegal
    base.bar = &D1::bar; // imaginary syntax, Illegal
    

    Having those function pointers inside the class allow the above (at the price of bigger object).

    struct VulkanInterface
    {
        void (*foo) ();
        void (*bar) (VulkanInterface& self);
        // ...
    };
    
    VulkanInterface makeVulkanInterface1() { return {my_foo, my_bar}; }
    VulkanInterface makeVulkanInterface2() { return {my_foo2, my_bar2}; }
    
    VulkanInterface v1 = makeVulkanInterface1();
    VulkanInterface v2 = makeVulkanInterface2();
    VulkanInterface v = v1;
    v = v2;
    v.foo = v1.foo;
    v.bar(v);