¿Miembros virtuales estáticos de C ++?

¿Es posible en C ++ tener una función miembro que sea static y virtual ? Aparentemente, no hay una manera directa de hacerlo ( static virtual member(); es un error de comstackción), pero ¿hay al menos una forma de lograr el mismo efecto?

ES DECIR:

 struct Object { struct TypeInformation; static virtual const TypeInformation &GetTypeInformation() const; }; struct SomeObject : public Object { static virtual const TypeInformation &GetTypeInformation() const; }; 

Tiene sentido usar GetTypeInformation() en una instancia ( object->GetTypeInformation() ) y en una clase ( SomeObject::GetTypeInformation() ), que puede ser útil para las comparaciones y vital para las plantillas.

La única forma en que puedo pensar involucra escribir dos funciones / una función y una constante, por clase, o usar macros.

¿Alguna otra solución?

No, no hay forma de hacerlo, ya que ¿qué pasaría cuando Object::GetTypeInformation() ? No puede saber qué versión de clase derivada llamar ya que no hay ningún objeto asociado a ella.

Tendrá que hacer que sea una función virtual no estática para que funcione correctamente; si también desea poder invocar la versión de una clase derivada específica de manera no virtual sin una instancia de objeto, también deberá proporcionar una segunda versión estática no virtual estática.

Muchos dicen que no es posible, daría un paso más y diría que no es significativo.

Un miembro estático es algo que no se relaciona con ninguna instancia, solo con la clase.

Un miembro virtual es algo que no se relaciona directamente con ninguna clase, solo con una instancia.

Entonces, un miembro virtual estático sería algo que no se relaciona con ninguna instancia o clase.

Me encontré con este problema el otro día: tenía algunas clases llenas de métodos estáticos pero quería usar la herencia y los métodos virtuales y reducir la repetición del código. Mi solución fue:

En lugar de usar métodos estáticos, use un singleton con métodos virtuales.

En otras palabras, cada clase debe contener un método estático al que llame para obtener un puntero a una sola instancia compartida de la clase. Puede hacer que los constructores reales sean privados o protegidos para que el código externo no pueda usarlo incorrectamente creando instancias adicionales.

En la práctica, usar un singleton es muy parecido a usar métodos estáticos, excepto que puede aprovechar la herencia y los métodos virtuales.

¡Es posible!

Pero qué es exactamente lo que podemos hacer, limitémonos. La gente a menudo quiere algún tipo de “función virtual estática” debido a la duplicación del código necesario para poder invocar la misma función mediante la llamada estática “SomeDerivedClass :: myfunction ()” y la llamada polimórfica “base_class_pointer-> myfunction ()”. El método “legal” para permitir tal funcionalidad es la duplicación de definiciones de funciones:

 class Object { public: static string getTypeInformationStatic() { return "base class";} virtual string getTypeInformation() { return getTypeInformationStatic(); } }; class Foo: public Object { public: static string getTypeInformationStatic() { return "derived class";} virtual string getTypeInformation() { return getTypeInformationStatic(); } }; 

¿Qué pasa si la clase base tiene una gran cantidad de funciones estáticas y la clase derivada tiene que anular todas y se olvidó de proporcionar una definición de duplicación para la función virtual. Correcto, obtendremos algún error extraño durante el tiempo de ejecución que es difícil de rastrear. Causa de duplicación de código es algo malo. El siguiente intenta resolver este problema (y quiero decir de antemano que es completamente seguro y no contiene magia negra como typeid o dynamic_cast 🙂

Por lo tanto, queremos proporcionar solo una definición de getTypeInformation () por clase derivada y es obvio que tiene que ser una definición de función estática porque no es posible llamar a “SomeDerivedClass :: getTypeInformation ()” si getTypeInformation () es virtual. ¿Cómo podemos llamar a la función estática de la clase derivada a través de puntero a la clase base? No es posible con vtable porque vtable almacena punteros solo para funciones virtuales y como decidimos no usar funciones virtuales, no podemos modificar vtable para nuestro beneficio. Entonces, para poder acceder a la función estática para la clase derivada a través de puntero a la clase base, tenemos que almacenar de alguna manera el tipo de un objeto dentro de su clase base. Un enfoque es hacer que la clase base se personalice usando un “patrón de plantilla curiosamente recurrente”, pero no es apropiado aquí y usaremos una técnica llamada “borrado de tipo”:

 class TypeKeeper { public: virtual string getTypeInformation() = 0; }; template class TypeKeeperImpl: public TypeKeeper { public: virtual string getTypeInformation() { return T::getTypeInformationStatic(); } }; 

Ahora podemos almacenar el tipo de un objeto dentro de la clase base “Objeto” con una variable “guardián”:

 class Object { public: Object(){} boost::scoped_ptr keeper; //not virtual string getTypeInformation() const { return keeper? keeper->getTypeInformation(): string("base class"); } }; 

En una clase derivada, el guardián debe inicializarse durante la construcción:

 class Foo: public Object { public: Foo() { keeper.reset(new TypeKeeperImpl()); } //note the name of the function static string getTypeInformationStatic() { return "class for proving static virtual functions concept"; } }; 

Agreguemos azúcar sintáctico:

 template void override_static_functions(T* t) { t->keeper.reset(new TypeKeeperImpl()); } #define OVERRIDE_STATIC_FUNCTIONS override_static_functions(this) 

Ahora las declaraciones de descendientes se ven así:

 class Foo: public Object { public: Foo() { OVERRIDE_STATIC_FUNCTIONS; } static string getTypeInformationStatic() { return "class for proving static virtual functions concept"; } }; class Bar: public Foo { public: Bar() { OVERRIDE_STATIC_FUNCTIONS; } static string getTypeInformationStatic() { return "another class for the same reason"; } }; 

uso:

 Object* obj = new Foo(); cout << obj->getTypeInformation() << endl; //calls Foo::getTypeInformationStatic() obj = new Bar(); cout << obj->getTypeInformation() << endl; //calls Bar::getTypeInformationStatic() Foo* foo = new Bar(); cout << foo->getTypeInformation() << endl; //calls Bar::getTypeInformationStatic() Foo::getTypeInformation(); //compile-time error Foo::getTypeInformationStatic(); //calls Foo::getTypeInformationStatic() Bar::getTypeInformationStatic(); //calls Bar::getTypeInformationStatic() 

Ventajas:

  1. menos duplicación de código (pero tenemos que llamar OVERRIDE_STATIC_FUNCTIONS en cada constructor)

Desventajas:

  1. OVERRIDE_STATIC_FUNCTIONS en cada constructor
  2. sobrecarga de memoria y rendimiento
  3. mayor complejidad

Problemas abiertos:

1) hay diferentes nombres para funciones estáticas y virtuales ¿cómo resolver la ambigüedad aquí?

 class Foo { public: static void f(bool f=true) { cout << "static";} virtual void f() { cout << "virtual";} }; //somewhere Foo::f(); //calls static f(), no ambiguity ptr_to_foo->f(); //ambiguity 

2) cómo llamar implícitamente OVERRIDE_STATIC_FUNCTIONS dentro de cada constructor?

Es posible. Haga dos funciones: estática y virtual

 struct Object{ struct TypeInformation; static const TypeInformation &GetTypeInformationStatic() const { return GetTypeInformationMain1(); } virtual const TypeInformation &GetTypeInformation() const { return GetTypeInformationMain1(); } protected: static const TypeInformation &GetTypeInformationMain1(); // Main function }; struct SomeObject : public Object { static const TypeInformation &GetTypeInformationStatic() const { return GetTypeInformationMain2(); } virtual const TypeInformation &GetTypeInformation() const { return GetTypeInformationMain2(); } protected: static const TypeInformation &GetTypeInformationMain2(); // Main function }; 

Si bien Alsk ya ha dado una respuesta bastante detallada, me gustaría agregar una alternativa, ya que creo que su implementación mejorada es demasiado complicada.

Comenzamos con una clase base abstracta, que proporciona la interfaz para todos los tipos de objetos:

 class Object { public: virtual char* GetClassName() = 0; }; 

Ahora necesitamos una implementación real. Pero para evitar tener que escribir los métodos estático y virtual, nuestras clases de objetos reales heredarán los métodos virtuales. Obviamente, esto solo funciona, si la clase base sabe cómo acceder a la función miembro estática. Entonces, necesitamos usar una plantilla y pasarle el nombre de clase real de objetos:

 template class ObjectImpl : public Object { public: virtual char* GetClassName() { return ObjectType::GetClassNameStatic(); } }; 

Finalmente, necesitamos implementar nuestros objetos reales. Aquí solo tenemos que implementar la función de miembro estático, las funciones de miembro virtual se heredarán de la clase de plantilla ObjectImpl, se creará una instancia con el nombre de la clase derivada, por lo que accederá a sus miembros estáticos.

 class MyObject : public ObjectImpl { public: static char* GetClassNameStatic() { return "MyObject"; } }; class YourObject : public ObjectImpl { public: static char* GetClassNameStatic() { return "YourObject"; } }; 

Agreguemos algunos códigos para probar:

 char* GetObjectClassName(Object* object) { return object->GetClassName(); } int main() { MyObject myObject; YourObject yourObject; printf("%s\n", MyObject::GetClassNameStatic()); printf("%s\n", myObject.GetClassName()); printf("%s\n", GetObjectClassName(&myObject)); printf("%s\n", YourObject::GetClassNameStatic()); printf("%s\n", yourObject.GetClassName()); printf("%s\n", GetObjectClassName(&yourObject)); return 0; } 

No, esto no es posible, porque las funciones de miembros estáticos carecen de this puntero. Y los miembros estáticos (tanto funciones como variables) no son realmente miembros de la clase per se. Simplemente son invocados por ClassName::member , y se adhieren a los especificadores de acceso de clase. Su almacenamiento se define en algún lugar fuera de la clase; el almacenamiento no se crea cada vez que crea una instancia de un objeto de la clase. Los punteros a los miembros de la clase son especiales en semántica y syntax. Un puntero a un miembro estático es un puntero normal en todos los aspectos.

las funciones virtuales de una clase necesitan this puntero y están muy unidas a la clase, por lo tanto, no pueden ser estáticas.

Bueno, es una respuesta bastante tardía, pero es posible usar el patrón de plantilla curiosamente recurrente. Este artículo de wikipedia tiene la información que necesita y también el ejemplo bajo polymorphism estático es lo que se le pide.

No, la función de miembro estático no puede ser virtual, ya que el concepto virtual se resuelve en tiempo de ejecución con la ayuda de vptr, y vptr es un miembro no estático de una clase. Debido a que la función miembro estática no puede acceder a vptr, el miembro estático puede no ser virtual

Creo que lo que estás tratando de hacer se puede hacer a través de plantillas. Estoy tratando de leer entre líneas aquí. Lo que intenta hacer es llamar a un método desde algún código, donde llama a una versión derivada pero el llamador no especifica qué clase. Ejemplo:

 class Foo { public: void M() {...} }; class Bar : public Foo { public: void M() {...} }; void Try() { xxx::M(); } int main() { Try(); } 

Desea que Try () llame a la versión Bar de M sin especificar Bar. La forma en que haces eso para la estática es usar una plantilla. Así que cámbialo así:

 class Foo { public: void M() {...} }; class Bar : public Foo { public: void M() {...} }; template  void Try() { T::M(); } int main() { Try(); } 

No, no es posible, ya que los miembros estáticos están vinculados en tiempo de comstackción, mientras que los miembros virtuales están vinculados en tiempo de ejecución.

Primero, las respuestas son correctas de que lo que el OP está solicitando es una contradicción en términos: los métodos virtuales dependen del tipo de tiempo de ejecución de una instancia; las funciones estáticas específicamente no dependen de una instancia, solo de un tipo. Dicho esto, tiene sentido que las funciones estáticas devuelvan algo específico a un tipo. Por ejemplo, tenía una familia de clases de MouseTool para el patrón de estado y comencé a tener cada una una función estática que devolvía el modificador de teclado que la acompañaba; Usé esas funciones estáticas en la función de fábrica que hizo la instancia correcta de MouseTool. Esa función comprobó el estado del mouse contra MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier (), etc. y luego creó la instancia apropiada. Por supuesto, más tarde quise comprobar si el estado era correcto, así que quería escribir algo como “if (keyboardModifier == dynamic_type (* state) :: keyboardModifier ())” (sin syntax C ++ real), que es lo que esta pregunta está haciendo .

Entonces, si te encuentras con ganas de esto, es posible que quieras solucionar tu problema. Aún así, entiendo el deseo de tener métodos estáticos y luego llamarlos dinámicamente en función del tipo dynamic de una instancia. Creo que Visitor Pattern puede darte lo que quieres. Te da lo que quieres. Es un poco de código adicional, pero podría ser útil para otros visitantes.

Ver: http://en.wikipedia.org/wiki/Visitor_pattern para el fondo.

 struct ObjectVisitor; struct Object { struct TypeInformation; static TypeInformation GetTypeInformation(); virtual void accept(ObjectVisitor& v); }; struct SomeObject : public Object { static TypeInformation GetTypeInformation(); virtual void accept(ObjectVisitor& v) const; }; struct AnotherObject : public Object { static TypeInformation GetTypeInformation(); virtual void accept(ObjectVisitor& v) const; }; 

Luego, para cada Objeto concreto:

 void SomeObject::accept(ObjectVisitor& v) const { v.visit(*this); // The compiler statically picks the visit method based on *this being a const SomeObject&. } void AnotherObject::accept(ObjectVisitor& v) const { v.visit(*this); // Here *this is a const AnotherObject& at compile time. } 

y luego defina el visitante base:

 struct ObjectVisitor { virtual ~ObjectVisitor() {} virtual void visit(const SomeObject& o) {} // Or = 0, depending what you feel like. virtual void visit(const AnotherObject& o) {} // Or = 0, depending what you feel like. // More virtual void visit() methods for each Object class. }; 

Luego, el visitante concreto que selecciona la función estática apropiada:

 struct ObjectVisitorGetTypeInfo { Object::TypeInformation result; virtual void visit(const SomeObject& o) { result = SomeObject::GetTypeInformation(); } virtual void visit(const AnotherObject& o) { result = AnotherObject::GetTypeInformation(); } // Again, an implementation for each concrete Object. }; 

finalmente, úsalo:

 void printInfo(Object& o) { ObjectVisitorGetTypeInfo getTypeInfo; Object::TypeInformation info = o.accept(getTypeInfo).result; std::cout << info << std::endl; } 

Notas:

  • Constness dejó como un ejercicio.
  • Usted devolvió una referencia de una estática. A menos que tenga un singleton, eso es cuestionable.

Si desea evitar los errores de copiar y pegar, donde uno de sus métodos de visita llama a la función estática incorrecta, podría usar una función de ayuda temporal (que no puede ser virtual) para su visitante con una plantilla como esta:

 struct ObjectVisitorGetTypeInfo { Object::TypeInformation result; virtual void visit(const SomeObject& o) { doVisit(o); } virtual void visit(const AnotherObject& o) { doVisit(o); } // Again, an implementation for each concrete Object. private: template  void doVisit(const T& o) { result = T::GetTypeInformation(); } }; 

Tal vez puedas probar mi solución a continuación:

 class Base { public: Base(void); virtual ~Base(void); public: virtual void MyVirtualFun(void) = 0; static void MyStaticFun(void) { assert( mSelf != NULL); mSelf->MyVirtualFun(); } private: static Base* mSelf; }; Base::mSelf = NULL; Base::Base(void) { mSelf = this; } Base::~Base(void) { // please never delete mSelf or reset the Value of mSelf in any deconstructors } class DerivedClass : public Base { public: DerivedClass(void) : Base() {} ~DerivedClass(void){} public: virtual void MyVirtualFun(void) { cout<<"Hello, it is DerivedClass!"< 

Como han dicho otros, hay 2 datos importantes:

  1. no hay this puntero al hacer una llamada de función estática y
  2. el puntero apunta a la estructura donde se usa la tabla virtual, o thunk, para buscar qué método de ejecución llamar.

Una función estática se determina en tiempo de comstackción.

Mostré este ejemplo de código en miembros estáticos de C ++ en clase ; muestra que puede llamar a un método estático dado un puntero nulo:

 struct Foo { static int boo() { return 2; } }; int _tmain(int argc, _TCHAR* argv[]) { Foo* pFoo = NULL; int b = pFoo->boo(); // b will now have the value 2 return 0; }