Неожиданное поведение после назначения объекта функции оболочке функции
Я искал ошибку в приложении, которую я наконец исправил, но не совсем понял.
Поведение может быть воспроизведено с помощью следующей простой программы:
#include <iostream>
#include <memory>
#include <functional>
struct Foo
{
virtual int operator()(void) { return 1; }
};
struct Bar : public Foo
{
virtual int operator()(void) override { return 2; }
};
int main()
{
std::shared_ptr<Foo> p = std::make_shared<Bar>();
std::cout << (*p)() << std::endl;
std::function<int(void)> f;
f = *p;
std::cout << f() << std::endl;
return 0;
}
Вывод строки
std::cout << (*p)() << std::endl;
это 2
, что, как я и ожидал, конечно.
Но вывод строки
std::cout << f() << std::endl;
это 1
. Это меня удивило. Я даже был удивлен, что назначение f = *p
разрешено и не вызывает ошибку.
Я не прошу обходной путь, потому что я установил его с помощью лямбды.
У меня вопрос: что происходит, когда я делаю f = *p
и почему вывод 1
, а не 2
?
Я воспроизвел проблему с помощью gcc (MinGW) и Visual Studio 2019.
Далее я хочу упомянуть, что вывод
Bar b;
std::function<int(void)> f1 = b;
std::cout << f1() << std::endl;
снова 2
.
Ответы
Ответ 1
Нарезка объектов происходит здесь.
Дана точка f = *p;
, p
имеет тип std::shared_ptr<Foo>
, затем тип *p
- Foo&
(вместо Bar&
). Даже оператор присваивания std::function
принимает аргумент по ссылке, но
4) Устанавливает цель *this
для вызываемой f
, как если бы она выполнялась function(std::forward<F>(f)).swap(*this);
.
Обратите внимание, что F
выше также выводится как Foo&
. И конструктор из std::function
принимает аргумент по значению, происходит разрезание объекта, возникает эффект, что f
назначается из объекта типа Foo
, который копируется фрагментом из *p
.
template< class F >
function( F f );
Ответ 2
Это обычная нарезка, скрытая под слоем std::function
и std::shared_ptr
.
f = *p;
допустимо, потому что *p
является вызываемым объектом с соответствующим operator()
, и это одна из вещей, которую вы можете заключить в std::function
.
Причина, по которой он не работает, заключается в том, что он копирует *p
- и это Foo&
, а не Bar&
.
Эта адаптация вашего последнего примера будет вести себя так же:
Bar b;
Foo& c = b;
std::function<int(void)> f1 = c;
std::cout << f1() << std::endl;
Ответ 3
нарезка
Это случай нарезки.
Причина - оператор присваивания std::function
(как показано в другом ответе), который гласит:
Устанавливает цель * this для вызываемого f, как будто путем выполнения Функция (станд :: вперед (е)). подкачки (* это) ;. Этот оператор не участвовать в разрешении перегрузки, если f не вызывается для аргумента типы Args... и возвращаемый тип R. (начиная с С++ 14)
https://en.cppreference.com/w/cpp/utility/functional/function/operator%3D
Если вы упростите и урезаете пример - вы легко увидите, что происходит:
Foo* p = new Bar;
Foo f;
f = *p;//<-- slicing here since you deref and then copy the object
Похоже, вы стремились получить указатель на переопределенную виртуальную функцию - к сожалению, нет простого способа развернуть поиск виртуальной функции, как это реализовано с помощью таблицы поиска во время выполнения. Тем не менее, простой обходной путь может заключаться в использовании лямбды для переноса (как упоминается в ОП):
f = [p]{return (*p)();};
Более подходящим решением может быть только использование reference_wrapper
:
f = std::ref(p);
Ответ 4
Статический тип указателя p
- Foo
.
Так что в этом заявлении
f = *p;
там левый операнд *p
имеет тип Foo
, то есть есть разрезание.