Virtual functions and std::function?

2019-07-17 00:10发布

问题:

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?

回答1:

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));


回答2:

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)(); });


回答3:

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.



回答4:

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()