C ++ miembros virtuales estáticos?

140

¿Es posible en C ++ tener una función miembro que sea ambas staticy virtual? Aparentemente, no hay una forma directa de hacerlo ( static virtual member();es un error de compilació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()tanto en una instancia ( object->GetTypeInformation()) como en una clase ( SomeObject::GetTypeInformation()), lo que puede ser útil para las comparaciones y vital para las plantillas.

Las únicas formas en que puedo pensar implica escribir dos funciones / una función y una constante, por clase, o usar macros.

¿Alguna otra solución?

cvb
fuente
12
Solo un comentario secundario: los métodos estáticos no se ejecutan en ninguna instancia, lo que significa que no tienen implícito este puntero. Dicho esto, la constfirma en un método marca el thispuntero implícito como constante y no se puede aplicar a los métodos estáticos, ya que carecen del parámetro implícito.
David Rodríguez - dribeas 01 de
2
@cvb: Consideraría seriamente reemplazar su ejemplo con código que no implique reflexión. Tal como está ahora, estás combinando dos cuestiones separadas (aunque relacionadas). Sí, y sé que han pasado 5 años y medio desde que lo preguntaste.
einpoklum
Una de las características implícitamente requeridas aquí es hacer que el compilador verifique que cada objeto en una jerarquía implemente una interfaz específica (donde uno o más del método es estático). Básicamente, una verificación virtual pura para el método estático tiene mucho sentido, ya que si olvida agregar el método estático, entonces el compilador debería fallar. virtual no es la palabra clave aquí, es más abstracto y resulta ser una especie de sinónimo en C ++, excepto en este caso específico. Desafortunadamente, no puedes hacerlo con C ++ actualmente.
xryl669

Respuestas:

75

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

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

Adam Rosenfield
fuente
8
Si piensa en la clase estática (o los miembros estáticos de las clases) como un singleton, todo se vuelve obvio, en su caso, simplemente debe llamarse Object :: GetTypeInformation, de la misma manera que llamando al método virtual regular en la instancia de la clase base . (Por supuesto, si C ++ soporta métodos estáticos virtuales)
Spook
13
Ese es un argumento completamente engañoso. Si usa la clase en lugar de un objeto, naturalmente usaría la versión de esa clase, en lugar de hacer un despacho virtual. Nada nuevo allí.
Deduplicador
54

Muchos dicen que no es posible, iría un paso más allá y diría que no tiene sentido.

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.

Rasmus Kaj
fuente
42
Es perfectamente significativo en lenguajes donde las clases son valores de primera clase, por ejemplo, Delphi tiene eso y también tiene métodos "virtuales estáticos".
Pavel Minaev
44
Exactamente. Una "función virtual" es (por definición) una función que está vinculada dinámicamente , es decir, se elige en tiempo de ejecución dependiendo del tipo dinámico de un objeto dado. Por lo tanto, no object = no virtual call.
Kos
77
También creo que los virtuales estáticos son significativos. Sería posible definir clases de interfaz e incluir métodos estáticos que deben implementarse en la clase derivada.
bkausbk
34
No es tan significativo para un static virtualmétodo, pero un método static puro virtual es muy significativo en una interfaz.
Bret Kuhns
44
Es perfectamente significativo tener un static const string MyClassSillyAdditionalName.
einpoklum
23

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 métodos virtuales y reducir la repetición de 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 única instancia compartida de la clase. Puede hacer que los verdaderos constructores 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.

Nate CK
fuente
Eso me costará rendimiento, a menos que el compilador pueda estar seguro de que: 1. En realidad es un singleton y 2. Nada se hereda de él, no creo que pueda optimizar toda la sobrecarga.
einpoklum
Si el rendimiento de este tipo de cosas te preocupa, entonces C # es probablemente el lenguaje incorrecto para ti.
Nate CK
3
Ah, buen punto. Obviamente, ha pasado un tiempo desde que pensé en esto desde que lo escribí en 2009. Permítanme decirlo de otra manera, entonces: si este tipo de problema de rendimiento le preocupa, entonces tal vez debería evitar el uso de la herencia por completo. El póster pedía específicamente métodos virtuales, por lo que es extraño que vengas aquí para quejarte sobre la sobrecarga de métodos virtuales.
Nate CK
15

¡Es posible!

Pero qué es exactamente posible, reduzcamos. 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 llamar a la misma función a través de la llamada estática "SomeDerivedClass :: myfunction ()" y la llamada polimórfica "base_class_pointer-> myfunction ()". El método "legal" para permitir dicha 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 cada una de ellas y se olvidó de proporcionar una definición duplicada para la función virtual? Bien, obtendremos un extraño error durante el tiempo de ejecución que es difícil de rastrear. Porque la duplicación de código es algo malo. Lo siguiente intenta resolver este problema (y quiero decir de antemano que es completamente seguro y no contiene magia negra como typeid's o dynamic_cast's :)

Por lo tanto, queremos proporcionar solo una definición de getTypeInformation () por clase derivada y es obvio que debe ser una definición de estáticafuncionan 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 del puntero a la clase base? No es posible con vtable porque vtable almacena punteros solo para funciones virtuales y dado que decidimos no usar funciones virtuales, no podemos modificar vtable para nuestro beneficio. Luego, para poder acceder a la función estática de la clase derivada a través del 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 sea estandarizada 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 T>
class TypeKeeperImpl: public TypeKeeper
{
public:
    virtual string getTypeInformation() { return T::getTypeInformationStatic(); }
};

Ahora podemos almacenar el tipo de un objeto dentro de la clase base "Object" con una variable "keeper":

class Object
{
public:
    Object(){}
    boost::scoped_ptr<TypeKeeper> 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<Foo>()); }
    //note the name of the function
    static string getTypeInformationStatic() 
    { return "class for proving static virtual functions concept"; }
};

Agreguemos azúcar sintáctico:

template<class T>
void override_static_functions(T* t)
{ t->keeper.reset(new TypeKeeperImpl<T>()); }
#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 a 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?

Alsk
fuente
+1 por esfuerzo, aunque no estoy seguro de que sea más elegante que simplemente delegar la funcionalidad a un singleton con métodos virtuales.
einpoklum
1
@einpoklum, puedo pensar en una situación en la que esto sea preferible. Supongamos que tenemos mucho código de cliente que ya llama a métodos estáticos. Cambiar de métodos estáticos a un singleton con métodos virtuales requeriría cambios en el código del cliente, mientras que la solución presentada anteriormente no es invasiva.
Alsk
La palabra clave "virtual" no es necesaria para "Foo :: getTypeInformation" y "TypeKeeperImpl :: getTypeInformation".
bartolo-otrit
12

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 tanto los métodos estáticos como los virtuales, 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 la clase de objetos reales:

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

Finalmente, necesitamos implementar nuestros objetos reales. Aquí solo necesitamos implementar la función miembro estática, las funciones miembro virtuales serán heredadas de la clase de plantilla ObjectImpl, instanciadas con el nombre de la clase derivada, para que acceda a sus miembros estáticos.

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

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

Agreguemos un código 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;
}

Anexo (12 de enero de 2019):

En lugar de utilizar la función GetClassNameStatic (), también puede definir el nombre de la clase como un miembro estático, incluso "en línea", que IIRC funciona desde C ++ 11 (no se asuste por todos los modificadores :)):

class MyObject : public ObjectImpl<MyObject>
{
public:
    // Access this from the template class as `ObjectType::s_ClassName` 
    static inline const char* const s_ClassName = "MyObject";

    // ...
};
Timo
fuente
11

Es posible. Realiza 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
};
Alexey Malistov
fuente
44
Además, los métodos estáticos no pueden ser constantes. Simplemente no tiene sentido, ¿qué instancia no van a mutar?
David Rodríguez - dribeas 01 de
1
Esto es principalmente solo duplicación de código. La idea es que las subclases solo necesiten tener el miembro const estático, no tener que tener código para acceder a él.
einpoklum
8

No, esto no es posible, porque las funciones miembro estáticas carecen de un thispuntero. Y los miembros estáticos (tanto funciones como variables) no son realmente miembros de clase per-se. Simplemente son invocados por ClassName::membery 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 sintaxis. Un puntero a un miembro estático es un puntero normal en todos los aspectos.

Las funciones virtuales en una clase necesitan el thispuntero y están muy acopladas a la clase, por lo tanto, no pueden ser estáticas.


fuente
1
Solo las funciones no estáticas necesitan un this puntero. Las funciones estáticas no son específicas de una instancia y no lo necesitarían. Entonces, esa no es una razón por la que los miembros estáticos virtuales son imposibles.
einpoklum
7

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

tropicana
fuente
3

Creo que lo que intentas hacer se puede hacer a través de plantillas. Estoy tratando de leer entre líneas aquí. Lo que está tratando de hacer es llamar a un método desde algún código, donde llama a una versión derivada pero la persona que llama 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 de hacerlo para las estadísticas es usar una plantilla. Así que cámbialo así:

class Foo {
public:
    void M() {...}
};

class Bar : public Foo {
public:
    void M() {...}
};

template <class T>
void Try()
{
    T::M();
}

int main()
{
    Try<Bar>();
}
zumalifeguard
fuente
1
Si sangra su espacio de código 4, puede formatearlo automáticamente. Alternativamente, creo que puede usar la marca de retroceso para lograr el mismo propósito en línea.
chollida el
1
Esto es lo obvio que me perdí. Gracias. Aún así, los miembros del pubis son raros.
allesblinkt
M () no es una función estática. ¿Cómo se llama T :: M ()?
DDukDDak99
3

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

Prabhat Kumar
fuente
2
Solo los métodos virtuales específicos de instancia requieren vtable de instancias. Podría tener una estática, una por clase, vtable. Y si desea conocer las instancias, solo señale desde la tabla vtable de la instancia también a la tabla estática de clase vtable.
einpoklum
2

No es posible, pero eso es solo por una omisión. No es algo que "no tenga sentido" como mucha gente parece reclamar. Para ser claros, estoy hablando de algo como esto:

struct Base {
  static virtual void sayMyName() {
    cout << "Base\n";
  }
};

struct Derived : public Base {
  static void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
  Derived::sayMyName(); // Also would work.
}

Esto es 100% algo que podría implementarse (simplemente no lo ha hecho), y diría que es algo útil.

Considere cómo funcionan las funciones virtuales normales. Elimine el staticsy agregue algunas otras cosas y tenemos:

struct Base {
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
}

Esto funciona bien y, básicamente, lo que sucede es que el compilador crea dos tablas, llamadas VTables, y asigna índices a las funciones virtuales como esta

enum Base_Virtual_Functions {
  sayMyName = 0;
  foo = 1;
};

using VTable = void*[];

const VTable Base_VTable = {
  &Base::sayMyName,
  &Base::foo
};

const VTable Derived_VTable = {
  &Derived::sayMyName,
  &Base::foo
};

A continuación, cada clase con funciones virtuales se aumenta con otro campo que apunta a su VTable, por lo que el compilador básicamente las cambia para que sean así:

struct Base {
  VTable* vtable;
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  VTable* vtable;
  void sayMyName() override {
    cout << "Derived\n";
  }
};

Entonces, ¿qué sucede realmente cuando llamas b->sayMyName()? Básicamente esto:

b->vtable[Base_Virtual_Functions::sayMyName](b);

(El primer parámetro se convierte this).

Bien, ¿cómo funcionaría con funciones virtuales estáticas? ¿Cuál es la diferencia entre las funciones miembro estáticas y no estáticas? La única diferencia es que este último obtiene un thispuntero.

Podemos hacer exactamente lo mismo con funciones virtuales estáticas: simplemente quite el thispuntero.

b->vtable[Base_Virtual_Functions::sayMyName]();

Esto podría admitir ambas sintaxis:

b->sayMyName(); // Prints "Base" or "Derived"...
Base::sayMyName(); // Always prints "Base".

Así que ignora a todos los detractores. Se hace tiene sentido. ¿Por qué no es compatible entonces? Creo que es porque tiene muy pocos beneficios e incluso podría ser un poco confuso.

La única ventaja técnica sobre una función virtual normal es que no necesita pasar thisa la función, pero no creo que eso suponga una diferencia apreciable en el rendimiento.

Significa que no tiene una función estática y no estática separada para los casos en que tiene una instancia y cuando no tiene una instancia, pero también puede ser confuso que solo sea realmente "virtual" cuando usa la llamada de instancia

Timmmm
fuente
0

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

PaulJWilliams
fuente
0

Primero, las respuestas son correctas de que lo que solicita el OP es una contradicción de 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 MouseTool para el patrón State y comencé a hacer que cada una tuviera una función estática que devolvía el modificador de teclado que lo acompañaba; Usé esas funciones estáticas en la función de fábrica que hicieron la instancia correcta de MouseTool. Esa función verificó el estado del mouse contra MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier (), etc. y luego instancia el apropiado. Por supuesto, más tarde quería comprobar si el estado era correcto, así que quería escribir algo como "

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

Ver: http://en.wikipedia.org/wiki/Visitor_pattern para antecedentes.

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 adecuada:

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:

  • La constidad se fue como ejercicio.
  • Has devuelto una referencia de una estática. A menos que tenga un singleton, eso es cuestionable.

Si desea evitar errores de copiar y pegar donde uno de sus métodos de visita llama a la función estática incorrecta, puede usar una función auxiliar con plantilla (que no puede ser virtual) a 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 <typename T>
    void doVisit(const T& o) {
        result = T::GetTypeInformation();
    }
};
Ben
fuente
los métodos estáticos virtuales, si existieran, no dependerían de nada en una instancia, pero la instancia necesitaría saber su tipo para invocarlos. Esto puede ser resuelto por un compilador (por ejemplo, utilizando una estructura de datos única por clase con punteros a métodos y miembros estáticos virtuales). Ciertamente no es una contradicción en términos.
einpoklum
Si es o no una contradicción en los términos es una cuestión de semántica. Uno podría imaginar que C ++ permite llamar a estadísticas desde una instancia (por ejemplo, en Foo foo; ... foo::bar();lugar de Foo::bar();). Eso no es diferente, decltype(foo)::bar();pero eso nuevamente estaría estáticamente vinculado. El enfoque de visitante parece una forma razonable de obtener este comportamiento sin simplemente hacer que el método estático sea un método const virtual.
Ben
0

Con c ++ puede usar herencia estática con el método crt. Por ejemplo, se usa ampliamente en la plantilla de ventana atl & wtl.

Ver https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Para ser simple, tiene una clase que se crea en sí misma como class myclass: public myancestor. Desde este punto, la clase myancestor ahora puede llamar a su función T :: YourImpl estática.


fuente
-1

Quizás 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!"<<endl; }
};

int main() {
    DerivedClass testCls;
    testCls.MyStaticFun(); //correct way to invoke this kind of static fun
    DerivedClass::MyStaticFun(); //wrong way
    return 0;
}
Jiez
fuente
Sí, lo sé, 4 años. Explicando el puntaje para aquellos que no quieren leer el código con tantos detalles. Base::mSelfse refiere a la instancia MÁS RECIENTEMENTE construida de cualquier clase derivada, incluso si esa instancia ha sido destruida . Así class D1 : public Base ...; class D2 : public Base ...; ...; D1* pd1 = new D1(); D2* pd2 = new D2(); pd1->MyStaticFun(); /* calls D2::MyVirtualFun() */ delete pd2; pd1->MyStaticFun(); /* calls via deleted pd2 */que NO es lo que se quiere.
Jesse Chisholm
-3

Como otros han dicho, hay 2 piezas importantes de información:

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

Una función estática se determina en tiempo de compilació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;
}
chollida
fuente
66
Técnicamente, este es un comportamiento indefinido. No puede deducir un puntero nulo por ningún motivo. Lo único que puede hacer con un puntero nulo es a) asignarle otro puntero yb) compararlo con otro puntero.
KeithB el
1
Además, solo puede compararlo para la igualdad (o desigualdad_ con otro puntero, sin ordenar. Es decir p < null, p >= nulletc., todos están indefinidos también.)
Pavel Minaev
1
@KeithB: para completar, también puede llamar de forma segura a eliminar en un puntero nulo.
Steve Rowe