У меня есть базовый класс с общим поведением и производные классы с переопределением виртуальных функций. Но если попытаться сохранить указатель на функцию как std::vector, я получу ошибку:

ISO C ++ запрещает использование адреса неквалифицированной нестатической функции-члена или заключенной в скобки для формирования указателя на функцию-член. Мне нужно что-то вроде этого:

class Base
{
public:
    virtual int fun1(int inp) = 0;
    virtual int fun2(int inp) = 0;
    int init(int inp, std::vector<std::function<int(int)>> Callbacks, int index)
    {
        if (index == 0)
            return Callbacks[0](inp);
        else
            return Callbacks[1](inp);
    }

    int run()
    {
        return init(5, {&fun1, &fun2}, 0);
    }
};

class A : public Base
{
    int fun1(int inp)
    {
        return inp * 10;
    }
    int fun1(int inp)
    {
        return inp * 100;
    }
};

class B : public Base
{
    int fun1(int inp)
    {
        return inp * 20;
    }
    int fun2(int inp)
    {
        return inp * 200;
    }
};

int main(int argc, char const *argv[])
{
    auto f = new B;
    int e = f->run();
    return 0;
}

Есть ли способ делать то, что я хочу? Может, как-нибудь привязать виртуальную функцию к контейнеру? Или, может быть, я могу установить лямбда-функцию как виртуальную? Когда я пытался объявить лямбда-функцию в базовом классе, когда я меняю функцию в производном классе, я получаю ошибки:

захват непеременной «Base :: fun1»

невозможно преобразовать «Base :: run () :: » в «std :: function

1
Wusiki Jeronii 15 Сен 2021 в 15:38

3 ответа

Лучший ответ

Вы получаете «указатель» на член с синтаксисом &Class::Member (Class::Member - это «полное» имя члена).

Это указатели только в абстрактном смысле, и их необходимо разыменовать относительно объекта.
Это можно сделать с помощью оператора ->* или .*, в зависимости от того, является ли левая часть указателем или нет.
Обратите внимание, что вы должны использовать this явно, если это релевантный объект.

Типом &Base::fun1 и &Base::fun2 является int (Base::*)(int), который не может быть преобразован в std::function<int(int)>.

Собираем вместе:

int init(int inp, std::vector<int (Base::*)(int)> Callbacks, int index)
{
    if (index == 0)
        return (this->*Callbacks[0])(inp); // The leftmost parentheses are necessary.
    else
        return (this->*Callbacks[1])(inp);
}

int run()
{
    return init(5, {&Base::fun1, &Base::fun2}, 0);
}

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

int init(int inp, std::vector<std::function<int(int))> Callbacks, int index)
{
    if (index == 0)
        return Callbacks[0](inp);
    else
        return Callbacks[1](inp);
}

int run()
{
    return init(5, 
                {[this](int i) { return fun1(i); },
                 [](int) { return 567; } }, 
                0);
}
1
molbdnilo 15 Сен 2021 в 13:21

В вашем коде есть несколько проблем:

  1. Указатели на функции-члены - это не то же самое, что указатели на функции, не являющиеся членами, статические или «свободные» функции. Это означает, что тип здесь std::function<int(int)> неверен, поскольку он предназначен для бесплатных или статических функций.

  2. Вызов указателя функции-члена отличается от обычного вызова функции. Вам необходимо указать объект, для которого вы вызываете указатель функции-члена < / а>. Начиная с C ++ 17, вы также можете вызывать указатель на функцию-член, используя более общий std::invoke.

  3. Вам нужно только один раз запустить Callbacks (возможно, у подрядчика) и использовать его в run позже.

  4. После передачи указателя на функцию-член вам понадобится синтаксис &ClassName::MemberFunction, который вы пропустили при вызове функции init(5, {&fun1, &fun2}, 0);.

  5. Base требовал виртуального деструктора для определенного поведения, если вы сохраняете потомки в указателе класса Base позже.

Ниже приведен пример кода в соответствии с приведенными выше изменениями:

#include <functional> // std::invoke

class Base
{
    // member function pointer type
    using MemFunPtrType = int(Base::*)(int);
    // to store the member functions pointers
    std::vector<MemFunPtrType> mMemberFunctionPts; 

public:
    Base() // initlize the mMemberFunctionPts once!
        : mMemberFunctionPts{ { &Base::fun1, &Base::fun2 } }
    {}
    virtual int fun1(int inp) = 0;
    virtual int fun2(int inp) = 0;
    virtual ~Base() = default;

    // run choose the correct member function as per the passed index!
    int run(std::size_t index)
    {
        if (2u == mMemberFunctionPts.size() && index == 0)
            return (this->*mMemberFunctionPts[index])(5);
            // or using std::invoke
            // return std::invoke(mMemberFunctionPts[index], this, 5);
        else
            return (this->*mMemberFunctionPts[index])(5);
    }
};

См. (живую демонстрацию здесь)

2
JeJo 15 Сен 2021 в 13:23

@PaulMcKenzie, спасибо. Теперь работает:

class Base
{
public:
    std::function<int(int)> fun1;
    std::function<int(int)> fun2;
    int init(int inp, std::vector<std::function<int(int)>> Callbacks, int index)
    {
        if (index == 0)
            return Callbacks[0](inp);
        else
            return Callbacks[1](inp);
    }

    int run()
    {
        return init(5, {&fun1, &fun2}, 0);
    }
};

class B : public Base
{
public:
    B()
    {
        fun1 = fun1B;
        fun2 = fun2B;
    }

private:
    std::function<int(int)> fun1B = [&](int inp)
    {
        return inp * 20;
    };
    std::function<int(int)> fun2B = [&](int inp)
    {
        return inp * 200;
    };
};

int main(int argc, char const *argv[])
{
    auto f = new B();
    int e = f->run();
    assert(e == 5*20);
    return 0;
}

Но приведенный выше пример выглядит более правильным.

0
Wusiki Jeronii 15 Сен 2021 в 13:11