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

По сути, у меня есть класс, скажем, Parameter, в котором есть переменная get и set.

У меня также есть базовый класс, скажем, Vehicle, который имеет метод registerParameter(...), который принимает указатель на функцию-член в качестве геттера и указатель на функцию-член в качестве установщика. Затем этот метод должен записать эти два указателя в объект класса параметров и поместить этот объект в вектор.

И последнее, но не менее важное: у нас есть производный класс, скажем, Car, и мы вызываем registerParameter(...) со строкой "color" в качестве имени параметра, а также геттером и сеттером из этого производного класса.

Пример в коде:

Файл параметров

#ifndef BASE_H
#define BASE_H
#include "base.h"

class Parameter
{
    std::string (Base::*get)();
void (Base::*set)(std::string);
};

#endif

Базовый файл

#ifndef PARAMETER_H
#define PARAMETER_H
#include <vector>
#include "parameter.h"

class Base
{
  public:
std::vector<Parameter> list;
void registerNew(std::string (Base::*get)(), void (Base::*set)(std::string))
    {
        Parameters parameter;
        parameter.get = get;
        parameter.set = set;
        list.push_back(parameter);
}
};

#endif

Производный файл

class Derived
{
  public:
    Derived derived() 
    {
        registerNew(&getColor, &setColor);
    }

    std::string getColor()
    {
        return this->color;
    }

    std::string setColor(std::string newColor)
    {
        this->color = newColor;
    }
  private:
    std::string color;
};

Я думал об этом уже несколько дней, и мне действительно нужно решение до вечера пятницы.


person Pascal Neubert    schedule 03.07.2014    source источник
comment
Итак, ваша проблема в том, что getColor и setColor неприемлемы в registerNew? Вы всегда можете немного ослабить правила и вместо получения строгого указателя функции просто получить функцию, получающую void*, но затем, когда вы захотите ее использовать, вам придется правильно ее использовать.   -  person in need of help    schedule 03.07.2014
comment
Несмотря на отсутствующий вопросительный знак в заголовке вопроса, он говорит о том, что мне нужно.   -  person Pascal Neubert    schedule 03.07.2014
comment
@inneedofhelp: Это была бы очень плохая идея. Гораздо лучше взять std::function<void(std::string)> и std::bind Derived*.   -  person MSalters    schedule 03.07.2014


Ответы (2)


Вы не можете делать то, что пытаетесь:

Типы std::string (Base::*)() и std::string (Derived::*)() очень разные. std::string (Derived::*)() нельзя автоматически преобразовать в std::string (Base::*)().

Возьмем следующий сценарий.

struct Base
{
    int foo() { return 10; }
};

struct Derived : Base
{
    int bar() { return 20; }
};

int main()
{
    Base base;

    int (Base::*bf)() = &Base::foo;
    (base.*bf)(); // Should be able to call Base:foo(). No problem.

    bf = &Derived::bar; // This is a compiler error. However, if this were allowed....
    (base.*bf)(); // Call Derived::bar()?? That will be a problem. base is not an
                  // instance of Derived.
}

Обновить

Вы можете сделать что-то вроде:

#include <string>
#include <vector>

class Base;

// Create a base class Functor that provides the interface to be used by
// Base.
struct Functor
{
   virtual ~Functor() {}
   virtual std::string get(Base& base) = 0;
   virtual void set(Base& base, std::string) = 0;
};

// Create a class template that implements the Functor interface.
template <typename Derived> struct FunctorTemplate : public Functor
{
   // typedefs for get and set functions to be used by this class.
   typedef std::string (Derived::*GetFunction)();
   typedef void (Derived::*SetFunction)(std::string);

   // The constructor that uses the get and set functions of the derived
   // class to do itw work.
   FunctorTemplate(GetFunction get, SetFunction set) : get_(get), set_(set) {}

   virtual ~FunctorTemplate() {}

   // Implement the get() function.
   virtual std::string get(Base& base)
   {
      return (reinterpret_cast<Derived&>(base).*get_)();
   }

   // Implement the set() function.
   virtual void set(Base& base, std::string s)
   {
      (reinterpret_cast<Derived&>(base).*set_)(s);
   }

   GetFunction get_;
   SetFunction set_;
};

class Base
{
   public:
      std::vector<Functor*> functorList;

      void registerFunctor(Functor* functor)
      {
         functorList.push_back(functor);
      }
};

class Derived : public Base
{
  public:
    Derived() 
    {
       // Register a FunctorTemplate.
       registerFunctor(new FunctorTemplate<Derived>(&Derived::getColor, 
                                                    &Derived::setColor));
    }

    std::string getColor()
    {
        return this->color;
    }

    void setColor(std::string newColor)
    {
        this->color = newColor;
    }
  private:
    std::string color;
};
person R Sahu    schedule 03.07.2014
comment
Есть ли альтернатива? Или решение обойти эту проблему? - person Pascal Neubert; 03.07.2014

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

template<typename DERIVED> class Base
{
public:
   class Parameter {
     std::string (DERIVED::*get)();
     void (DERIVED::*set)();
   };
private:
   std::list<Parameter> list;
   // ...
};

class Derived : public Base<Derived> // !!!
{
   registerNew(&Derived::getColor, &Derived::setColor);
};

Это решение известно как Curiously Recurring Template Pattern (CRTP).

person MSalters    schedule 03.07.2014
comment
@PascalNeubert: это не обязательно, просто удобно (сохраняет повторение параметра шаблона). - person MSalters; 03.07.2014