1

Мой вопрос об использовании методов std :: function для класса. Предположим, у меня есть следующие иерархии классов:Использование std :: function для memberfunctions

class Foo { 
public: 
    virtual void print() { 
     cout << "In Foo::print()" << endl; 
    } 

    virtual void print(int) { 
     cout << "In Foo::print(int)" << endl; 
    } 
}; 

class Bar : public Foo { 
public: 
    virtual void print() override { 
     cout << "In Bar::print()" << endl; 
    } 

    virtual void print(int) override { 
     cout << "In Bar::print(int)" << endl; 
    } 
} 

Теперь есть еще одна функция, которая должна динамически назвать один из двух методов класса зависит от его ввода:

void call(Foo* foo, void (Foo::*func)(void)) { 
    (foo->*func)(); 
} 

Foo* foo = new Foo(); 
Bar* bar = new Bar(); 
call(foo, &Foo::print); 
call(bar, &Foo::print); 

Когда я компилирую приведенный выше код фрагмент кода с помощью г ++/лязг ++, он работает, как и ожидалось, где выход:

In Foo::print() 
In Bar::print() 

Мои вопросы, то:

  1. , так как есть две функции (перегруженные) с тем же именем: печать, когда я передать адрес функции класса: &Foo::print, как же компилятор знает, что я на самом деле вызова Foo::print(void) но не Foo::print(int)?

  2. есть другой способ, что я могу обобщить код, указанный выше, так что второй параметр void call(Foo*, xxx) может быть передан с использованием как Foo::print(void) и Foo::print(int)

  3. есть в любом случае для достижения этой функции с помощью новой функции в C++ 11 std::function? Я понимаю, что для использования метода std::function с использованием метода нестатического класса я должен использовать std::bind для привязки каждого метода класса к определенному объекту класса, но это было бы слишком неэффективно для меня, потому что у меня есть много объектов класса, которые будут связаны.

+0

Вы сказали, что в прототипе 'вызова (Foo * Foo, аннулируются (Foo :: * FUNC) (аннулируются))', таким образом, вы будете get '(void)' перегрузка –

+0

@SeverinPappadeux это правильно.Но когда я передаю '& Foo :: print' при передаче аргумента, как компилятор решил, что передать? –

ответ

1

Поскольку существуют две функции (перегруженные) с тем же именем: print, когда я передать адрес функции класса: &Foo::print, как же компилятор знает, что я на самом деле вызова Foo::print(void) но не Foo::print(int)?

Это допускается из-за [over.over]/p1:

Применение перегруженного имени функции без аргументов разрешено в некоторых контекстах функции, указателя на функцию или указатель на функцию-член для определенной функции из набора перегрузки.

Компилятор может использовать целевой тип параметра-типа-списка, чтобы определить, какие функции от перегрузки установлен элемент указатель на относится:

Применение перегруженного имени функции без аргументы разрешаются в определенных контекстах функции, a указатель на функцию или указатель на функцию-член для определенной функции из набора перегрузки. Функция имени считается названным набором перегруженных функций в таких контекстах. Выбранная функция - это тип, идентичный типу функции целевого типа, требуемого в контексте. [Примечание: ..] Мишень может быть

        - объект или ссылка инициализируется (8.5, 8.5.3, 8.5.4),
        - левая сторона присваивания (5.18),
        - параметр функции (5.2.2),
        - [..]

Название Foo:print представляет собой набор перегрузки, который компилятор просматривает, чтобы найти соответствие. Целевой тип Foo::print(void) присутствует в наборе перегрузки, поэтому компилятор разрешает имя этой перегрузке.

Есть еще один способ, что я могу обобщить код, приведенный выше, так что второй параметр void call(Foo*, xxx) может быть передан с использованием как Foo::print(void) и Foo::print(int)

Существует не общий способ сделать это с само название. Имя должно быть разрешено для перегрузки. Вместо этого попробуйте изменить код, чтобы принять объект функции, как лямбда:

template<class Callable> 
void call(Foo* foo, Callable&& callback) { 
    callback(foo); 
} 

int main() 
{ 
    call(foo, [] (Foo* f) { f->print(); f->print(1); }); 
} 
+0

Это потрясающе! Объяснение перегруженного набора, а не единственное имя, имеет для меня прекрасный смысл, и последний лямбда-трюк с семантикой перемещения является изящным и эффективным. Большое спасибо! –

0

Во-первых, это std::bind (почти) полностью устарели на C++ 11 лямбдах. Не используйте std :: bind, если вы можете помочь. Одним из них является гораздо яснее, чем другие, используя ваш пример кода:

const auto lambda = [=] { foo->print(); }; // Clear! 
const auto binderv = std::bind(static_cast<void(Foo::*)()>(&Foo::print), foo); // Gets the void version 
const auto binderi = std::bind(static_cast<void(Foo::*)(int)>(&Foo::print), foo, std::placeholders::_1); // Gets the int version 

//const auto binderv2 = std::bind(&Foo::print, foo); // Error! Can't tell which Foo::print() 
//const auto binderi2 = std::bind(&Foo::print, foo, std::placeholders::_1); // Error! Can't tell which Foo::print() 

lambda(); // prints "void" 
binderv(); // prints "void" 
binderi(1); // prints "int" 

Во-вторых, как компилятор знает, который перегружен функции для вызова? Точно так же было бы, если бы вы использовали функции, не являющиеся членами:

#include <iostream> 

void call(void (*fn)()) 
{ 
    fn(); 
} 

void print() { std::cout << "void\n"; } 
void print(int) { std::cout << "int\n"; } 

int main() 
{ 
    call(&print); // prints "void" 
} 

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

Lambdas или std::function могут обертывать любую из функций-членов, но обратите внимание, что вы не можете перегружать функцию на разных подписях std::function. См. here.


Update:

Правильный способ обработки вашего вопроса # 3 - иметь одну функции вызова функции с резко различными подписями, как у вас - это использовать некоторый посредник как функтор (лямбда , std::function, std::bind, ручная работа), чтобы стереть различия.

std::function<void()> объекты, имеющие одну и ту же подпись, независимо от того, какие функции вы вызываете, в качестве их подписи.std::function дороже (с точки зрения хранения и вызова), чем лямбда, но имеет то преимущество, что имеет имя-имя, которое вы можете использовать, если вам нужно сохранить его в контейнере или что-то в этом роде. Lambdas иногда может быть отстроен компилятором, если вы правильно играете в карты, поэтому эффективность может все же способствовать использованию лямбда.

+0

Спасибо за объяснение! 'const auto lambda' хорош, но для меня это не сработает, потому что у меня много таких объектов, как foo, скажем,> 1000. Я предполагаю использовать такие методы, мне нужно создать отдельную лямбду для каждого такого объекта, которая будет очень неэффективным. Благодаря! –

+0

См. Мое обновление по этому вопросу. – metal

+0

Да, я согласен. В любом случае, спасибо за решение! –

Смежные вопросы