Шаблон функции-члена C ++ в производном классе, как его вызвать из базового класса? - PullRequest
3 голосов
/ 26 мая 2010

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

У меня есть (пока не обращайте внимания на наследование и сосредоточьтесь на X):

class Base {};

class X : public Base {
private:
    double m_double;
public:
    template<class A> friend 
    void state( A& a, const X& x ) {
        data( a, x.m_double, "m_double" );
    }   
};

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

class XmlArchive {...}; //One Accessor
template<class T>
void data( XmlArchive& a, const T& t, const std::string& str ) {
//read data and serialize it to xml Archive
}

class ParameterList {...}; //Another Accessor
template<class T>
void data( ParameterList& a, const T& t, const std::string& str ) {
//put data in a parameter list 
}

Тогда я могу написать:

X myX;

XmlArchive myArchive;
state( myArchive, myX );

ParameterList myParameters;
state( myParameters, myX );

Фантастика, повторное использование кода! : D Однако следующее (явно) не удается:

Base* basePtr = new X; //This would come from factory really, I should not know the type X
state( myParameters, *basePtr ); //Error

Цель состоит в том, чтобы сделать этот последний вызов успешным. Что я учел (и почему это не приемлемо):

Первый вариант: заставить все Accessors наследовать от общего базового класса, скажем, AccessorBase, затем записать в Base

virtual state( AccessorBase& a ) const = 0;

и реализовать необходимый код в X (синтаксис для вызова состояния будет незначительно отличаться, но это можно исправить). Проблема заключается в том, что AccessorBase должен иметь виртуальную функцию для каждого возможного типа, которая является вторым аргументом в функции (ах) данных. Поскольку эти типы могут быть определяемыми пользователем классами (см. Случай композиции классов, X, в которой Y является членом данных), я не понимаю, как заставить эту стратегию работать.

Второй вариант: создать виртуальную функцию в базе для каждого аксессора. Это нарушает принцип открытия / закрытия, так как добавление нового класса Accessor (скажем, TxtArchive) потребует модификации базового класса.

Я понимаю, почему виртуальная функция-член не может быть шаблонизирована (возможно, разные vtbls в разных единицах компиляции). Однако мне кажется, что должно быть решение этой проблемы ... Бэйс знает, что это действительно тип X, и тип Accessor всегда является явным, так что это вопрос поиска способа вызова (для Аксессор типа XmlArchive):

state( xmlArchive, x ); //xmlArchive of type XmlArchive, x of type X

, который даст результат.

Подводя итог, я хотел бы позвонить:

state( myParameters, *basePtr );

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

Мне кажется, что boost :: serialize делает нечто похожее, но я не могу понять, как (может быть, это повторное внедрение отношений наследования в C ++ с помощью шаблонов, я вижу функцию This (), возвращающую самый производный указатель, но это действительно сбивает с толку ...)

Заранее еще раз спасибо за помощь!

Ответы [ 4 ]

1 голос
/ 23 июня 2011

Для этого вы можете использовать шаблон vistor:

class IVisitor
{
  public:
    virtual void on_data( double, const char * )=0;
    virtual void on_data( std::string, const char * )=0;
}; 

class Base 
{
  public:
    virtual void visit( IVisitor * v )=0;
};

class X : public Base 
{
  private:
    double m_double;
    std::string m_string;
  public:
    void visit( IVisitor * v)
    {
        v->on_data( m_double, "m_double" );
        v->on_data( m_string, "m_string" );
    }   
};

Base * base = ...;
XmlArchive ar;  // This must derive from IVisitor
base->visit(&ar);
ParameterList pl; // This must derive from IVisitor
base->visit(pl);

Если вам не нравится реализовывать каждый из различных типов on_data в IVisitor, вы можете использовать boost::any - но, по моему опыту, ветвления, требуемые в реализациях этой функции, не стоят этого.

1 голос
/ 26 мая 2010

Я полагаю, что вы можете сделать это, используя dynamic_cast или приведение в стиле C, если вы знаете, что у вас есть объект X, приведите Base * к X * и вызовите свой метод (который должен быть статическим в вашем примере.) Если ваша цепочка наследования глубже и вы не знаете, есть ли у вас X, Y или Z во время компиляции, вы все равно можете это сделать, но вам нужно будет включить RTTI.

Итак, подведем итог:

X::state( myParameters, *(X*)(basePtr) );

Или, если у вас включен RTTI:

X::state( myParameters, *dynamic_cast<X*>(basePtr) );

В сценарии X, Y, Z вам потребуются три ветви с элементами Z :: State, Y :: State и X :: State, вызывающие правильную ветвь в зависимости от типа времени выполнения basePtr.

switch(basePtr->get_type())
{
    case TYPE_X:
        X::state( myParameters, *(X*)(basePtr) );
        break;
    case TYPE_Y:
        Y::state( ... );
        break;
}

Вы поняли.

0 голосов
/ 23 июня 2011

Добавьте объявление о дружбе на свою базу, чтобы оно стало

class Base 
{
   template<class A> friend void state( A& a, const X& x );
};
0 голосов
/ 22 марта 2011

Не уверен, что я полностью понимаю параметры того, что вы спрашиваете, но мое чувство паука сигнализирует, что вы ищете применение любопытно повторяющегося шаблона :

class StateBase {
  virtual void state() = 0;
};

template<typename T>
class StateMixin : public StateBase {
  void state() {
    T::data();
  }
};

class MyType : public StateMixin<MyType>
{
  void data() {};
}

Здесь state () определяется с помощью StateMixin и может вызываться из любого экземпляра StateBase, но компилятором он создается отдельно для каждого пользовательского типа, реализующего данные.

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