The mechanics of extension via free functions or member functions

后端 未结 5 1827
自闭症患者
自闭症患者 2021-02-14 12:42

Loads of C++ libraries, the standard included, allow you to adapt your objects for use in the libraries. The choice is often between a member function or a free function in the

5条回答
  •  旧巷少年郎
    2021-02-14 13:14

    If you're just looking for a concrete example, consider the following:

    #include 
    #include 
    #include 
    
    namespace NS
    {
        enum direction { forward, backward, left, right };
    
        struct vehicle { virtual ~vehicle() { } };
    
        struct Car : vehicle
        {
            void MoveForward(int units) // (1)
            {
                std::cout << "in NS::Car::MoveForward(int)\n";
            }
        };
    
        void MoveForward(Car& car_, int units)
        {
            std::cout << "in NS::MoveForward(Car&, int)\n";
        }
    }
    
    template
    class HasMoveForwardMember // (2)
    {
        template
        struct sfinae_impl { };
    
        typedef char true_t;
        struct false_t { true_t f[2]; };
    
        static V* make();
    
        template
        static true_t check(U*, sfinae_impl* = 0);
        static false_t check(...);
    
    public:
        static bool const value = sizeof(check(make())) == sizeof(true_t);
    };
    
    template::value>
    struct MoveForwardDispatcher // (3)
    {
        static void MoveForward(V& v_, int units) { v_.MoveForward(units); }
    };
    
    template
    struct MoveForwardDispatcher // (3)
    {
        static void MoveForward(V& v_, int units) { NS::MoveForward(v_, units); }
    };
    
    template
    typename std::enable_if::value>::type // (4)
    mover(NS::direction d, V& v_)
    {
        switch (d)
        {
        case NS::forward:
            MoveForwardDispatcher::MoveForward(v_, 1); // (5)
            break;
        case NS::backward:
            // ...
            break;
        case NS::left:
            // ...
            break;
        case NS::right:
            // ...
            break;
        default:
            assert(false);
        }
    }
    
    struct NonVehicleWithMoveForward { void MoveForward(int) { } }; // (6)
    
    int main()
    {
        NS::Car v; // (7)
        //NonVehicleWithMoveForward v;  // (8)
        mover(NS::forward, v);
    }
    

    HasMoveForwardMember (2) is a metafunction that checks for the existence of a member function of that name with the signature void(V::*)(int) in a given class V. MoveForwardDispatcher (3) uses this information to call the member function if it exists or falls back to calling a free function if it doesn't. mover simply delegates the invocation of MoveForward to MoveForwardDispatcher (5).

    The code as-posted will invoke Car::MoveForward (1), but if this member function is removed, renamed, or has its signature changed, NS::MoveForward will be called instead.

    Also note that because mover is a template, a SFINAE check must be put in place to retain the semantics of only allowing objects derived from NS::vehicle to be passed in for v_ (4). To demonstrate, if one comments out (7) and uncomments (8), mover will be called with an object of type NonVehicleWithMoveForward (6), which we want to disallow despite the fact that HasMoveForwardMember::value == true.

    (Note: If your standard library does not come with std::enable_if and std::is_base_of, use the std::tr1:: or boost:: variants instead as available.)

    The way this sort of code is usually used is to always call the free function, and implement the free function in terms of something like MoveForwardDispatcher such that the free function simply calls the passed in object's member function if it exists, without having to write overloads of that free function for every possible type that may have an appropriate member function.

提交回复
热议问题