2008-08-29 34 views
10

¿Hay alguna forma de tener un tipo de miembro estático virtual en C++?Simulando un miembro estático virtual de una clase en C++?

Por ejemplo:

class BaseClass { 
    public: 
     BaseClass(const string& name) : _name(name) {} 
     string GetName() const { return _name; } 
     virtual void UseClass() = 0; 
    private: 
     const string _name; 
}; 


class DerivedClass : public BaseClass { 
    public: 
     DerivedClass() : BaseClass("DerivedClass") {} 
     virtual void UseClass() { /* do something */ } 
}; 

Sé que este ejemplo es trivial, pero si tengo un vector de datos complejos que va a ser siempre el mismo para todos clase derivada pero se necesita para ser accedido desde la base métodos de clase?

class BaseClass { 
    public: 
     BaseClass() {} 
     virtual string GetName() const = 0; 
     virtual void UseClass() = 0; 
}; 


class DerivedClass : public BaseClass { 
    public: 
     DerivedClass() {} 
     virtual string GetName() const { return _name; } 
     virtual void UseClass() { /* do something */ } 
    private: 
     static const string _name; 
}; 

string DerivedClass::_name = "DerivedClass"; 

Esta solución no me satify porque necesito reimplementar la _name miembro y su descriptor de acceso GetName() en cada clase. En mi caso, tengo varios miembros que siguen el comportamiento _name y décimas de clases derivadas.

¿Alguna idea?

Respuesta

8

Aquí es una solución:

struct BaseData 
{ 
    const string my_word; 
    const int my_number; 
}; 

class Base 
{ 
public: 
    Base(const BaseData* apBaseData) 
    { 
     mpBaseData = apBaseData; 
    } 
    const string getMyWord() 
    { 
     return mpBaseData->my_word; 
    } 
    int getMyNumber() 
    { 
     return mpBaseData->my_number; 
    } 
private: 
    const BaseData* mpBaseData; 
}; 

class Derived : public Base 
{ 
public: 
    Derived() : Base(&sBaseData) 
    { 
    } 
private: 
    static BaseData sBaseData; 
} 

BaseData Derived::BaseData = { "Foo", 42 }; 
2

Parece que la respuesta está en la pregunta: el método que sugirió parece ser la dirección correcta, excepto que si tiene una gran cantidad de miembros compartidos, puede reunirlos en una estructura o clase y más allá de eso como argumento para el constructor de la clase base.

Si insiste en tener los miembros "compartidos" implementados como miembros estáticos de la clase derivada, es posible que pueda generar automáticamente el código de las clases derivadas. XSLT es una gran herramienta para autogenerar clases simples.

En general, el ejemplo no muestra la necesidad de miembros "estáticos virtuales", porque para fines como estos no necesita herencia, en su lugar debe usar la clase base y que acepte los valores apropiados en el constructor - quizás creando una instancia única de los argumentos para cada "subtipo" y pasándole un puntero para evitar la duplicación de los datos compartidos. Otro enfoque similar es usar plantillas y pasar como el argumento de la plantilla una clase que proporciona todos los valores relevantes (esto se conoce comúnmente como el patrón de "Política").

Para concluir, a los fines del ejemplo original, no hay necesidad de tales miembros "estáticos virtuales". Si aún cree que se necesitan para el código que está escribiendo, intente elaborar y agregar más contexto.

Ejemplo de lo que he descrito anteriormente:

class BaseClass { 
    public: 
     BaseClass(const Descriptor& desc) : _desc(desc) {} 
     string GetName() const { return _desc.name; } 
     int GetId() const { return _desc.Id; } 
     X GetX() connst { return _desc.X; } 
     virtual void UseClass() = 0; 
    private: 
     const Descriptor _desc; 
}; 


class DerivedClass : public BaseClass { 
    public: 
     DerivedClass() : BaseClass(Descriptor("abc", 1,...)) {} 
     virtual void UseClass() { /* do something */ } 
}; 

class DerDerClass : public BaseClass { 
    public: 
     DerivedClass() : BaseClass("Wowzer", 843,...) {} 
     virtual void UseClass() { /* do something */ } 
}; 

me gustaría dar más detalles sobre esta solución, y tal vez dar una solución al problema de inicialización:

Con un pequeño cambio, se puede implementar el diseño descrito anteriormente sin crear necesariamente una nueva instancia del "descriptor" para cada instancia de una clase derivada.

Puede crear un objeto único, DescriptorMap, que llevará a cabo la única instancia de cada descriptor, y lo utilizan en la construcción de los objetos derivados de este modo:

enum InstanceType { 
    Yellow, 
    Big, 
    BananaHammoc 
} 

class DescriptorsMap{ 
    public: 
     static Descriptor* GetDescriptor(InstanceType type) { 
      if (_instance.Get() == null) { 
       _instance.reset(new DescriptorsMap()); 
      } 
      return _instance.Get()-> _descriptors[type]; 
     } 
    private: 
     DescriptorsMap() { 
      descriptors[Yellow] = new Descriptor("Yellow", 42, ...); 
      descriptors[Big] = new Descriptor("InJapan", 17, ...) 
      ... 
     } 

     ~DescriptorsMap() { 
      /*Delete all the descriptors from the map*/ 
     } 

     static autoptr<DescriptorsMap> _instance; 
     map<InstanceType, Descriptor*> _descriptors; 
} 

Ahora podemos hacer esto:

class DerivedClass : public BaseClass { 
    public: 
     DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.BananaHammoc)) {} 
     virtual void UseClass() { /* do something */ } 
}; 

class DerDerClass : public BaseClass { 
    public: 
     DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.Yellow)) {} 
     virtual void UseClass() { /* do something */ } 
}; 

Al final de la ejecución, cuando el tiempo de ejecución de C realiza desinicializaciones, también llama al destructor de objetos estáticos, incluido nuestro autoptr, que elimina nuestra instancia de DescriptorsMap.

Así que ahora tenemos una única instancia de cada descriptor que también se está eliminando al final de la ejecución.

Tenga en cuenta que si el único propósito de la clase derivada es proporcionar los datos del "descriptor" relevante (es decir, en lugar de implementar funciones virtuales) entonces debe conformarse con hacer que la clase base no sea abstracta y crear un instancia con el descriptor apropiado cada vez.

1

@Hershi: el problema con este enfoque es que cada instancia de cada clase derivada tiene una copia de los datos, lo que puede ser costoso de alguna manera.

Quizás podrías probar algo como esto (estoy diciendo cosas sin un ejemplo de compilación, pero la idea debería ser clara).


#include <iostream> 
#include <string> 
using namespace std; 

struct DerivedData 
{ 
    DerivedData(const string & word, const int number) : 
    my_word(word), my_number(number) {} 
    const string my_word; 
    const int my_number; 
}; 

class Base { 
public: 
    Base() : m_data(0) {} 
    string getWord() const { return m_data->my_word; } 
    int getNumber() const { return m_data->my_number; } 
protected: 
    DerivedData * m_data; 
}; 


class Derived : public Base { 
public: 
    Derived() : Base() { 
    if(Derived::s_data == 0) { 
     Derived::s_data = new DerivedData("abc", 1); 
    } 
    m_data = s_data; 
    } 
private: 
    static DerivedData * s_data; 
}; 


DerivedData * Derived::s_data = 0; 

int main() 
{ 
    Base * p_b = new Derived(); 
    cout getWord() << endl; 
} 

En cuanto a la pregunta de seguimiento sobre cómo eliminar el objeto estático: la única solución que viene a la mente es el uso de un puntero inteligente, algo así como el Boost shared pointer.

1

Acepto la sugerencia de Hershi de utilizar una plantilla como la "clase base". Según lo que describes, suena más como un uso para plantillas en lugar de subclases.

Se puede crear una plantilla de la siguiente manera (no han tratado de compilar este):

 

template <typename T> 
class Object 
{ 
public: 

    Object(const T& newObject) : yourObject(newObject) {} ; 
    T GetObject() const { return yourObject } ; 
    void SetObject(const T& newObject) { yourObject = newObject } ; 

protected: 

    const T yourObject ; 
} ; 

class SomeClassOne 
{ 
public: 

    SomeClassOne(const std::vector& someData) 
    { 
    yourData.SetObject(someData) ; 
    } 

private: 

    Object<std::vector<int>> yourData ; 
} ; 
 

Esto le permitirá utilizar los métodos de la clase de plantilla para modificar los datos que sean necesarios dentro de sus clases personalizadas que utilizan el datos y compartir los diversos aspectos de la clase de plantilla.

Si usted es la intención de utilizar la herencia, entonces es posible que tenga que recurrir a las "alegrías" del uso de un puntero void * en sus BaseClass y hacer frente a la fundición, etc.

Sin embargo, con base en su explicación , parece que necesitas plantillas y no herencia.

0

Suena como si estuviera tratando de evitar tener que duplicar el código en las clases de hojas, ¿por qué no acaba de derivar una clase base intermedia de la clase base. esta clase intermedia puede contener los datos estáticos y todas sus clases de hojas derivan de la clase base intermedia. Esto presupone que se desea una pieza estática de datos mantenida sobre todas las clases derivadas, que parece serlo a partir de su ejemplo.

Cuestiones relacionadas