Does std::bind discard type information of paramet

2019-02-24 07:16发布

问题:

Case where the problem occours

Please consider the following c++ code:

#include <functional>
#include <iostream>
#include <string>

// Superclass
class A {
    public:
    virtual std::string get() const {
        return "A";
    }
};

// Subclass
class B : public A {
    public:
    virtual std::string get() const {
        return "B";
    }
};

// Simple function that prints the object type
void print(const A &instance) {
    std::cout << "It's " << instance.get() << std::endl;
}

// Class that holds a reference to an instance of A
class State {
    A &instance;
    public:
    State(A &instance) : instance(instance) { }
    void run() {

        // Invokes print on the instance directly
        print(instance);

        // Creates a new function by binding the instance
        // to the first parameter of the print function, 
        // then calls the function. 
        auto func = std::bind(&print, instance);    
        func();
    }    
};

int main() {
    B instance;
    State state(instance);

    state.run();
}

In this example, we have two classes A and B. B inherits from class A. Both classes implement a simple virtual method that returns the type name.

There is also a simple method, print, that accepts a reference to an instance of A and prints the type.

The class State holds a reference to an instance of A. The class also has a simple method that calls print by two different means.

Where it gets odd

The sole method in state first calls print directly. Since we supply an instance of B int the main method, the output is It's B, as expected.

For the second call, however, we bind the instance to the first parameter of print using std::bind. Then we call the resulting function without any arguments.

In this case, however, the output is It's A. I would have expected the output It's B, as before, since it is still the same instance.

If I declare the parameters as pointers instead of references, std::bind works as expected. I also placed some logging into the constructors of both classes to verify that no instances are created accidentally.

Why does this happen? Does std::bind discard some type information in this case? To my understanding, this must not happen since the method invocation should be managed by a vtable lookup via runtime.

回答1:

This is just object slicing. Pass the instance by reference:

auto func = std::bind(&print, std::ref(instance));
//                            ^^^^^^^^

To explain this a bit more: Like most C++ standard library types, the result type of a bind expression owns all its bound state. This means you can take this value and pass it around freely and store it and come back to it later in a different context, and you can still call it with all its bound state ready for action.

Therefore, in your code, the bind object was constructed with a copy of instance. But since instance wasn't a complete object, you caused slicing to happen.

By contrast, my code copies a std::reference_wrapper<A> into the bind object, and that's essentially a pointer. It doesn't own the instance object, so I need to keep it alive as long as the bind object may get called, but it means that the bound call is dispatched polymorphically to the complete object.