Virtual functions and std::function?

2019-07-17 00:13发布

Consider the following code in 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 does not seem to do the right thing with virtual functions. 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?

4条回答
We Are One
2楼-- · 2019-07-17 00:40

your code doesn't pass instance of derived class to std::function, instead, it constructs new base object by copying instance of derived, which is passed to std::function.

查看更多
We Are One
3楼-- · 2019-07-17 00:52

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 copies its arguments to be able to run later, as already suggested in the comments to the question.
Therefore you can just avoid slicing objects and copy something else that is able to deal correctly with polymorphism.
As an example:

std::function f1([ptr1](){ (*ptr1)(); });
std::function f2([ptr2](){ (*ptr2)(); });
查看更多
Ridiculous、
4楼-- · 2019-07-17 00:55

A simple way to avoid the slicing copy made by std::function is to bind the method operator() to the object it should be applied.

So you could write:

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()
查看更多
霸刀☆藐视天下
5楼-- · 2019-07-17 00:59

You can use a std::reference_wrapper, or the convenient std::ref. std::function will use SOO (small object optimization) in this case, so the object won't be copied/moved (avoiding the slicing problem). However, you won't get the deduction guide so you need to specify the template arguments.

std::function<void()> f1(std::ref(*ptr1));
std::function<void()> f2(std::ref(*ptr2));
查看更多
登录 后发表回答