Указатели на методы класса C ++ - PullRequest
3 голосов
/ 11 сентября 2008

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

class MyClass
{
protected:
    bool CaseMethod1( int abc, const std::string& str )
    {
        cout << "case 1:" << str;
        return true;
    }

    bool CaseMethod2( int abc, const std::string& str )
    {
        cout << "case 2:" << str;
        return true;
    }

    bool CaseMethod3( int abc, const std::string& str )
    {
        cout << "case 3:" << str;
        return true;
    }

public:
    bool TestSwitch( int num )
    {   
        bool ( MyClass::*CaseMethod )( int, const std::string& );

        switch ( num )
        {
            case 1: CaseMethod = &MyClass::CaseMethod1;
                    break;
            case 2: CaseMethod = &MyClass::CaseMethod2;
                    break;
            case 3: CaseMethod = &MyClass::CaseMethod3;
                    break;
        }

        ...

        bool res = CaseMethod( 999, "hello world" );

        ...

        reurn res;
    }
};

Мой вопрос - это правильный путь? Должен ли я рассмотреть что-нибудь, что Boost может предложить?

Редактировать ...

Хорошо, моя ошибка - я должен вызывать метод следующим образом:

bool res = ( (*this).*CaseMethod )( 999, "Hello World" );

Ответы [ 7 ]

8 голосов
/ 11 сентября 2008

Имеется указатель на член-функцию. Это решит вашу проблему. Я удивлен, что ваша функция "TestSwitch" компилируется, так как синтаксис вызова немного отличается от того, что вы могли ожидать. Должно быть:

bool res = (this->*CaseMethod)( 999, "hello world" );

Однако, вы можете найти комбинацию boost :: function и boost :: bind, которая немного упростит задачу, поскольку вы можете избежать странного синтаксиса вызова.

boost::function<bool(int,std::string)> f=
    boost::bind(&MyClass::CaseMethod1,this,_1,_2);

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

boost::function<bool(MyClass*,int,std::string)> f=
    boost::bind(&MyClass::CaseMethod1,_1,_2,_3);

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

3 голосов
/ 11 сентября 2008

Вы также можете создать поиск (если ваш диапазон ключей разумный), чтобы в итоге вы написали:

this->*Methods[num]( 999, "hello world" );

Это также удаляет переключатель и делает очистку более целесообразной.

1 голос
/ 11 сентября 2008

Без более широкого контекста трудно найти правильный ответ, но я здесь шью три варианта:

  • оставайтесь с нормальным оператором переключения, не нужно ничего делать. Это наиболее вероятное решение

  • использовать указатели на функцию-член в сочетании с массивом, как говорит @Simon, или может быть с картой. Для оператора case с большим количеством дел это может быть быстрее.

  • разбивает класс на несколько классов, каждый из которых содержит одну функцию для вызова, и использует виртуальные функции. Это, наверное, лучшее решение, купить его потребуется серьезный рефаторинг. Рассмотрим шаблоны GoF, такие как State или Visitor или некоторые другие.

1 голос
/ 11 сентября 2008

Я не вижу разницы между вашим вызовом и простым вызовом метода в операторе switch.

Нет, различий в семантике или читабельности нет.

Единственное отличие, которое я вижу, состоит в том, что вы берете указатель на метод и поэтому запрещаете компилятору встроить его или оптимизируете любой вызов этого метода.

1 голос
/ 11 сентября 2008

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

bool res = this->*CaseMethod( 999, "hello world" );

С другой стороны, я бы порекомендовал boost :: mem_fn - у вас будет меньше шансов испортить его. ;)

0 голосов
/ 12 сентября 2008

Существуют и другие подходы, такие как использование абстрактного базового класса или специализированных шаблонных функций.

Я опишу идею базового класса.

Вы можете определить абстрактный базовый класс

class Base { virtual bool Method(int i, const string& s) = 0; };

Затем напишите каждый из ваших дел в качестве подкласса, например

class Case1 : public Base { virtual bool Method(..) { /* implement */; } };

В какой-то момент вы получите переменную num, которая указывает, какой тест выполнить. Вы можете написать фабричную функцию, которая принимает это число (я назову его which_case) и возвращает указатель на Base, а затем вызывает метод из этого указателя.

Base* CreateBase(int which_num) { /* metacode: return new Case[which_num]; */ }
// ... later, when you want to actually call your method ...
Base* base = CreateBase(23);
base->Method(999, "hello world!");
delete base;  // Or use a scoped pointer.

Кстати, это приложение заставляет меня хотеть, чтобы C ++ поддерживал статические виртуальные функции или что-то вроде «type» как встроенный тип - но это не так.

0 голосов
/ 11 сентября 2008

Нет ничего плохого в приведенном здесь локализованном примере, но указатели на методы класса часто сложно сохранить в безопасности, если вы используете их в более широком контексте, например, вне класса, на который они указывают или в сочетании со сложным деревом наследования. То, как компиляторы обычно управляют указателями на методы, отличается от «обычных» указателей (поскольку есть дополнительная информация, выходящая за пределы только точки ввода кода), и, следовательно, существует множество ограничений на то, что вы можете с ними делать.

Если вы просто сохраняете простые указатели так, как вы описываете, то у вас все будет хорошо, но для более сложных применений вы можете взглянуть на более обобщенную систему функторов, такую ​​как boost :: bind . Они могут указывать практически на любой указатель вызываемого кода, а также могут связывать аргументы экземпляров функций, если это необходимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...