代码之家  ›  专栏  ›  技术社区  ›  vbstb

C++函数指针中的协方差和逆变

  •  1
  • vbstb  · 技术社区  · 6 年前

    考虑一下这个

    class Base { };
    
    class Derived : public Base { };
    
    Base *f1(Derived *) { return {}; }
    Derived *f2(Derived *) { return {}; }   // covariant
    Base *f3(Base *) { return {}; } // contravariant
    Derived *f4(Base *) { return {}; } // covariant & contravariant
    
    using Callback = Base *(*)(Derived *);
    
    Callback pfunc1 = f1;   // works of course
    
    // These won't work...
    Callback pfunc2 = f2;
    Callback pfunc3 = f3;
    Callback pfunc4 = f4;
    
    
    // So I have to make a wrapper for each
    Base *f2_wrap(Derived *d)
    {
        return f2(d);
    }
    
    Base *f3_wrap(Derived *d)
    {
        return f3(d);
    }
    
    Base *f4_wrap(Derived *d)
    {
        return f4(d);
    }
    
    // Now it works
    Callback pfunc2 = f2_wrap;
    Callback pfunc3 = f3_wrap;
    Callback pfunc4 = f4_wrap;
    

    为什么我不能设置一个函数指针指向一个 Derived 对象作为返回值,或具有 Base 对象作为参数(在C委托中工作)吗?

    我知道我可以使用包装函数,但为什么它不是语言特性的一部分呢?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Cheers and hth. - Alf    6 年前

    只有当类型完全匹配时,原始函数指针才与赋值兼容。

    但是,您可以使用 std::function :

    #include <functional>
    
    struct Base{};
    struct Derived: Base{};
    
    auto f1( Derived* ) -> Base*        { return 0; }
    auto f2( Derived* ) -> Derived*     { return 0; }   // covariant
    auto f3( Base* )    -> Base*        { return 0; }   // contravariant
    auto f4( Base* )    -> Derived*     { return 0; }   // covariant & contravariant
    
    auto main()
        -> int
    {
        using Callback = std::function<auto( Derived* ) -> Base*>;
    
        Callback pfunc1 = f1;   // works
        Callback pfunc2 = f2;   // works
        Callback pfunc3 = f3;   // works
        Callback pfunc4 = f4;   // works
    }
    

    重写虚函数的规则不太允许:支持原始指针和引用类型的协变结果,但仅此而已。无反向方差。

    #include <functional>
    
    struct Base{};
    struct Derived: Base{};
    
    struct F{ virtual auto f( Derived* ) -> Base* = 0; };
    
    #define R override { return 0; }
    struct F1: F { auto f( Derived* ) -> Base*      R };
    struct F2: F { auto f( Derived* ) -> Derived*   R };   // covariant, OK
    struct F3: F { auto f( Base* )    -> Base*      R };   // !contravariant
    struct F4: F { auto f( Base* )    -> Derived*   R };   // !covariant & contravariant
    

    mingw g++7.3.0编译结果:

    > g++ -c 2.cpp
    2.cpp:11:21: error: 'Base* F3::f(Base*)' marked 'override', but does not override
     struct F3: F { auto f( Base* )    -> Base*      R };   // !contravariant
                         ^
    2.cpp:12:21: error: 'Derived* F4::f(Base*)' marked 'override', but does not override
     struct F4: F { auto f( Base* )    -> Derived*   R };   // !covariant & contravariant
    

    对于协方差对原始指针和引用结果类型的限制在实践中不是问题。例如,一个明显具有智能指针结果的协变函数可以很容易地表示为一个非虚拟重载,调用具有原始指针结果的虚拟函数。缺乏对反向方差的支持在实践中同样不是一个问题,但原因很简单,一个人永远不需要它。

        2
  •  0
  •   Bruno Romano    6 年前

    C++是这样设计的,不接受不同的参数,即使它是派生类。 看看这条线 Cast function pointers that differs by argument type

    C语言有一个虚拟机,不允许多重继承,所以C语言在这种情况下有更多的控制,你不能比较C和C++。

    如果您想使用Cast,可以这样做,但是您假设在派生类和基类方面有任何错误,如果您做了错误的事情,它将使您的程序崩溃。

    class Base { };
    
    class Derived : public Base { };
    
    Base *f1(Derived *) { return {}; }
    Derived *f2(Derived *) { return {}; }   // covariant
    Base *f3(Base *) { return {}; } // contravariant
    Derived *f4(Base *) { return {}; } // covariant & contravariant
    
    using Callback = Base *(*)(Derived *);
    
    Callback pfunc1 = f1;   // works of course
    
    // These won't work (now it will work)...
    Callback pfunc2 = (Callback)(f2); //explict cast
    Callback pfunc3 = (Callback)f3;
    Callback pfunc4 = (Callback)f4;