虚函数和std::function?

Virtual functions and std::function?

考虑以下 C++17 中的代码:

#include <iostream>
#include <functional>

struct base
{
    base() {std::cout << "base::base" << std::endl;}
    virtual ~base() {std::cout << "base::~base" << std::endl;}
    virtual void operator()() {std::cout << "base::operator()" << std::endl;}
};

struct derived1: base
{
    derived1() {std::cout << "derived1::derived1" << std::endl;}
    virtual ~derived1() {std::cout << "derived1::~derived1" << std::endl;}
    virtual void operator()() {std::cout << "derived1::operator()" << std::endl;}
};

struct derived2: base
{
    derived2() {std::cout << "derived2::derived2" << std::endl;}
    virtual ~derived2() {std::cout << "derived2::~derived2" << std::endl;}
    virtual void operator()() {std::cout << "derived2::operator()" << std::endl;}
};

int main(int argc, char* argv[])
{
    base* ptr1 = new derived1();
    base* ptr2 = new derived2();
    std::function f1(*ptr1);
    std::function f2(*ptr2);
    std::invoke(*ptr1);     // calls derived1::operator()
    std::invoke(*ptr2);     // calls derived2::operator()
    std::invoke(f1);        // calls base::operator()
    std::invoke(f2);        // calls base::operator()
    delete ptr1;
    delete ptr2;
    return 0;
}

std::function 似乎没有用虚函数做正确的事情。有什么方法可以使 std::invoke(*ptrN)std::invoke(fN) 的行为相同吗?或者有没有办法创建一个新的函数包装器来处理虚函数?

您的代码不会将派生实例 class 传递给 std::function,而是通过复制派生实例构造新的基础对象,该实例将传递给 std::function

您可以使用 std::reference_wrapper,或方便的 std::refstd::function 将在这种情况下使用 SOO(小对象优化),因此对象不会是 copied/moved(避免 slicing problem)。但是,您不会获得演绎指南,因此您需要指定模板参数。

std::function<void()> f1(std::ref(*ptr1));
std::function<void()> f2(std::ref(*ptr2));

Would there be any way to make std::invoke(*ptrN) and std::invoke(fN) behave the same way? Or would there be any way to create a new function wrapper that would deal with virtual functions?

std::function 复制其参数以便稍后能够 运行,正如问题评论中已经建议的那样。
因此,您可以避免切片对象并复制能够正确处理多态性的其他内容。
例如:

std::function f1([ptr1](){ (*ptr1)(); });
std::function f2([ptr2](){ (*ptr2)(); });

避免由 std::function 制作的切片副本的一个简单方法是 绑定 方法 operator() 到它应该应用的对象。

所以你可以这样写:

auto f1 = std::bind(&base::operator(), ptr1);
auto f2 = std::bind(&base::operator(), ptr2);
std::invoke(f1);                          // calls derived1::operator()
std::invoke(f2);                          // calls derived2::operator()