Отбрасывает ли std::bind информацию о типах параметров в С++ 11?

Случай возникновения проблемы

Пожалуйста, рассмотрите следующий код С++:

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

В этом примере у нас есть два класса A и B. B наследуется от класса A. Оба класса реализуют простой виртуальный метод, который возвращает имя типа.

Существует также простой метод print, который принимает ссылку на экземпляр A и печатает тип.

Класс State содержит ссылку на экземпляр A. В классе также есть простой метод, который вызывает print двумя разными способами.

Где это становится странным

Единственный метод в состоянии сначала вызывает print напрямую. Поскольку мы передаем экземпляр B в основной метод, вывод будет It's B, как и ожидалось.

Однако для второго вызова мы привязываем экземпляр к первому параметру print, используя std::bind. Затем мы вызываем полученную функцию без каких-либо аргументов.

Однако в этом случае выход равен It's A. Я ожидал вывода It's B, как и раньше, так как это все тот же экземпляр.

Если я объявлю параметры как указатели, а не ссылки, std::bind будет работать как положено. Я также поместил некоторые журналы в конструкторы обоих классов, чтобы убедиться, что экземпляры не создаются случайно.

Почему это происходит? Отбрасывает ли std::bind некоторую информацию о типе в этом случае? Насколько я понимаю, этого не должно происходить, поскольку вызов метода должен управляться поиском в виртуальной таблице через среду выполнения.


person Emiswelt    schedule 19.12.2015    source источник


Ответы (1)


Это всего лишь нарезка объектов. Передайте экземпляр по ссылке:

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

Чтобы объяснить это немного больше: Как и большинство типов стандартной библиотеки C++, тип результата выражения bind владеет всем своим связанным состоянием. Это означает, что вы можете взять это значение и передать его свободно, сохранить его и вернуться к нему позже в другом контексте, и вы все еще можете вызывать его со всем его связанным состоянием, готовым к действию.

Поэтому в вашем коде объект привязки был создан с использованием копии instance. Но поскольку instance не был полным объектом, вы вызвали нарезку.

Напротив, мой код копирует std::reference_wrapper<A> в объект привязки, и это, по сути, указатель. Он не владеет экземпляром объекта, поэтому мне нужно поддерживать его в рабочем состоянии до тех пор, пока может быть вызван объект связывания, но это означает, что связанный вызов полиморфно отправляется на полный объект.

person Kerrek SB    schedule 19.12.2015
comment
Почему это еще не передано в качестве ссылки? Переменная, переданная std::bind, является ссылкой, аргумент для печати также является ссылкой. Дополнительные/временные экземпляры строятся. - person Emiswelt; 19.12.2015
comment
@Emiswelt: Волшебный вопрос в том, что это. Что-то передается по ссылке, несомненно, но не то, что вы думаете. В вашем случае сам объект привязки содержит нарезанную копию вашего экземпляра (которую он должным образом передает по ссылке), тогда как в моем коде он содержит ссылочную оболочку. - person Kerrek SB; 19.12.2015
comment
@Emiswelt std::bind передаст свой параметр либо путем копирования, либо путем перемещения. Исключение std::reference_wrapper. - person 101010; 19.12.2015
comment
Обратите внимание, что bind на самом деле имеет специальную обработку для reference_wrappers - она ​​всегда разворачивает их. - person T.C.; 19.12.2015
comment
@T.C.: Вы имеете в виду через INVOKE? - person Kerrek SB; 19.12.2015
comment
@KerrekSB Нет, прежде чем дело дойдет до INVOKE. См. первый пункт в [func.bind.bind]/10. - person T.C.; 19.12.2015
comment
@T.C.: Ах да, get() встроен в bind - вы не полагаетесь на функцию преобразования. - person Kerrek SB; 19.12.2015
comment
@T.C.: Кстати, вы обновите страницу cppreference на std::invoke? - person Kerrek SB; 19.12.2015