web-dev-qa-db-fra.com

Membres virtuels statiques C ++?

Est-il possible en C++ d'avoir une fonction membre qui est à la fois static et virtual? Apparemment, il n'y a pas de méthode simple pour le faire (static virtual member(); est une erreur de compilation), mais existe-t-il au moins un moyen d'obtenir le même effet?

C'EST À DIRE:

struct Object
{
     struct TypeInformation;

     static virtual const TypeInformation &GetTypeInformation() const;
};

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

Il est logique d'utiliser GetTypeInformation() à la fois sur une instance (object->GetTypeInformation()) et sur une classe (SomeObject::GetTypeInformation()), ce qui peut être utile pour les comparaisons et essentiel pour les modèles.

La seule façon dont je puisse penser consiste à écrire deux fonctions/une fonction et une constante, par classe, ou d’utiliser des macros.

Toute autre solution?

130
cvb

Non, il n'y a aucun moyen de le faire, car que se passerait-il lorsque vous appelez Object::GetTypeInformation()? Il ne peut pas savoir quelle version de la classe dérivée appeler car il n'y a pas d'objet associé.

Vous devrez en faire une fonction virtuelle non statique pour fonctionner correctement; si vous souhaitez également pouvoir appeler la version d'une classe dérivée spécifique de manière non virtuelle sans instance d'objet, vous devez également fournir une deuxième version redondante statique non virtuelle.

73
Adam Rosenfield

Beaucoup disent que ce n'est pas possible, je voudrais aller un peu plus loin et dire que cela n'a pas de sens.

Un membre statique est quelque chose qui ne concerne aucune instance, mais seulement la classe.

Un membre virtuel est quelque chose qui ne concerne pas directement une classe, mais seulement une instance.

Ainsi, un membre virtuel statique serait quelque chose qui ne se rapporterait à aucune instance ni à aucune classe.

55
Rasmus Kaj

L’autre jour, j’ai rencontré ce problème: j’avais des classes remplies de méthodes statiques mais je voulais utiliser les méthodes d’héritage et virtuelles et réduire la répétition du code. Ma solution était:

Au lieu d'utiliser des méthodes statiques, utilisez un singleton avec des méthodes virtuelles.

En d'autres termes, chaque classe doit contenir une méthode statique que vous appelez pour obtenir un pointeur sur une seule instance partagée de la classe. Vous pouvez rendre les vrais constructeurs privés ou protégés afin que le code extérieur ne puisse pas en abuser en créant des instances supplémentaires.

En pratique, l’utilisation d’un singleton ressemble beaucoup à l’utilisation de méthodes statiques, sauf que vous pouvez tirer parti de l’héritage et des méthodes virtuelles.

21
Nate C-K

C'est possible!

Mais qu'est-ce qui est possible, précisons-le. Les gens veulent souvent une sorte de "fonction virtuelle statique" en raison de la duplication du code nécessaire pour pouvoir appeler la même fonction via l'appel statique "SomeDerivedClass :: myfunction ()" et l'appel polymorphe "base_class_pointer-> myfunction ()". La méthode "légale" pour autoriser une telle fonctionnalité est la duplication des définitions de fonction:

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(); }
};

Et si la classe de base a un grand nombre de fonctions statiques et que la classe dérivée doit écraser chacune d’elles et que l’on oublie de fournir une définition de duplication pour la fonction virtuelle. Bien, nous aurons une erreur étrange pendant runtime qui est difficile à localiser. Causer une duplication de code est une mauvaise chose. Ce qui suit tente de résoudre ce problème (et je tiens à dire à l’avance qu’il est totalement sans danger pour le type et qu'il ne contient aucune magie noire comme celle de typeid ou dynamic_cast :)

Donc, nous voulons fournir une seule définition de getTypeInformation () par classe dérivée et il est évident qu'il doit s'agir d'une définition de la fonction statique car il n'est pas possible d'appeler "SomeDerivedClass :: getTypeInformation ( ) "si getTypeInformation () est virtuel. Comment pouvons-nous appeler une fonction statique de la classe dérivée via un pointeur sur la classe de base? Cela n’est pas possible avec vtable car vtable stocke les pointeurs uniquement vers les fonctions virtuelles et, comme nous avons décidé de ne pas utiliser de fonctions virtuelles, nous ne pouvons pas modifier vtable à notre avantage. Ensuite, pour pouvoir accéder à une fonction statique pour une classe dérivée via un pointeur sur la classe de base, nous devons stocker en quelque sorte le type d'un objet dans sa classe de base. Une approche consiste à créer une classe de base modélisée à l'aide de "modèle de modèle curieusement récurrent", mais ce n'est pas approprié ici et nous allons utiliser une technique appelée "type effacement":

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

Nous pouvons maintenant stocker le type d'un objet dans la classe de base "Object" avec une variable "keeper":

class Object
{
public:
    Object(){}
    boost::scoped_ptr<TypeKeeper> keeper;

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

};

Dans une classe dérivée, le gardien doit être initialisé pendant la construction:

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"; }
};

Ajoutons le sucre syntaxique:

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

Maintenant, les déclarations de descendants ressemblent à:

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"; }
};

usage:

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()

Avantages:

  1. moins de duplication de code (mais nous devons appeler OVERRIDE_STATIC_FUNCTIONS dans chaque constructeur)

Désavantages:

  1. OVERRIDE_STATIC_FUNCTIONS dans chaque constructeur
  2. surcharge de mémoire et de performance
  3. complexité accrue

Questions ouvertes:

1) Il existe différents noms pour les fonctions statiques et virtuelles. Comment résoudre l'ambiguïté ici?

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) comment appeler implicitement OVERRIDE_STATIC_FUNCTIONS dans chaque constructeur?

14
Alsk

Bien qu'Alsk ait déjà donné une réponse assez détaillée, je voudrais ajouter une alternative, car je pense que son implémentation améliorée est trop compliquée.

Nous commençons avec une classe de base abstraite, qui fournit l'interface pour tous les types d'objet:

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

Nous avons maintenant besoin d'une implémentation réelle. Mais pour éviter d'écrire à la fois les méthodes statiques et virtuelles, nous allons faire en sorte que nos classes d'objets héritent des méthodes virtuelles. Cela ne fonctionne évidemment que si la classe de base sait comment accéder à la fonction membre static. Nous devons donc utiliser un modèle et lui transmettre le nom actuel de la classe d'objets:

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

Enfin, nous devons implémenter nos objets réels. Ici, nous avons seulement besoin d'implémenter la fonction de membre statique, les fonctions de membre virtuel seront héritées de la classe de modèle ObjectImpl, instanciées avec le nom de la classe dérivée, afin qu'elle puisse accéder à ses membres statiques.

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

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

Ajoutons du code à tester:

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;
}

Addendum (12 janvier 2019):

Au lieu d'utiliser la fonction GetClassNameStatic (), vous pouvez également définir le nom de la classe en tant que membre statique, même "inline", que l'IIRC fonctionne depuis C++ 11 (n'ayez pas peur de tous les modificateurs :)):

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

    // ...
};
12
Timo

C'est possible. Faire deux fonctions: statique et virtuelle

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
};
11
Alexey Malistov

Non, cela n’est pas possible, car les fonctions des membres statiques n’ont pas de pointeur this. Et les membres statiques (les fonctions et les variables) ne sont pas vraiment des membres de classe en tant que tels. Ils viennent juste d'être invoqués par ClassName::member, et adhérer aux spécificateurs d'accès aux classes. Leur stockage est défini quelque part en dehors de la classe; le stockage n'est pas créé à chaque fois que vous instanciez un objet de la classe. Les pointeurs vers les membres de la classe sont spéciaux en sémantique et en syntaxe. Un pointeur sur un membre statique est un pointeur normal à tous égards.

les fonctions virtuelles d'une classe ont besoin du pointeur this et sont très couplées à la classe. Elles ne peuvent donc pas être statiques.

8
Mads Elvheim

Eh bien, c'est une réponse assez tardive, mais il est possible d'utiliser le modèle de modèle curieusement récurrent. Cet article wikipedia contient les informations dont vous avez besoin et vous trouverez également l'exemple sous polymorphisme statique.

6
tropicana

Non, la fonction de membre statique ne peut pas être virtuelle. Le concept virtuel étant résolu au moment de l'exécution à l'aide de vptr, et vptr étant un membre non statique d'un class.due, cette fonction de membre statique ne peut accéder à vptr. soyez pas virtuel.

3
Prabhat Kumar

Je pense que ce que vous essayez de faire peut être fait à travers des modèles. J'essaie de lire entre les lignes ici. Ce que vous essayez de faire, c'est d'appeler une méthode à partir d'un code, où elle appelle une version dérivée, mais l'appelant ne spécifie pas quelle classe. Exemple:

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

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

void Try()
{
    xxx::M();
}

int main()
{
    Try();
}

Vous voulez que Try () appelle la version Bar de M sans spécifier Bar. Pour ce faire, vous utilisez un modèle. Alors changez-le comme suit:

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

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

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

int main()
{
    Try<Bar>();
}
2
zumalifeguard

Non, ce n'est pas possible, car les membres statiques sont liés au moment de la compilation, alors que les membres virtuels sont liés au moment de l'exécution.

0
PaulJWilliams

Premièrement, les réponses sont exactes: l'OP demande une contradiction: les méthodes virtuelles dépendent du type d'exécution d'une instance; Les fonctions statiques ne dépendent pas spécifiquement d'une instance, mais simplement d'un type. Cela dit, il est logique que les fonctions statiques renvoient quelque chose de spécifique à un type. Par exemple, j'avais une famille de classes MouseTool pour le modèle State et j'ai commencé à avoir chacune une fonction statique renvoyant le modificateur de clavier qui l'accompagnait; J'ai utilisé ces fonctions statiques dans la fonction d'usine qui ont créé la bonne instance MouseTool. Cette fonction vérifiait l'état de la souris par rapport à MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier (), etc., puis instanciait celui qui était approprié. Bien sûr, plus tard, je voulais vérifier si l'état était correct, alors je voulais écrire quelque chose comme "if (keyboardModifier == dynamic_type (* state) :: keyboardModifier ())" (pas la vraie syntaxe C++), c'est ce que cette question demande .

Donc, si vous voulez cela, vous pouvez retoucher votre solution. Je comprends tout de même le désir d’avoir des méthodes statiques, puis de les appeler dynamiquement en fonction du type dynamique d’une instance. Je pense que le modèle de visiteur peut vous donner ce que vous voulez. Cela vous donne ce que vous voulez. C'est un peu de code supplémentaire, mais cela pourrait être utile pour les autres visiteurs.

Voir: http://en.wikipedia.org/wiki/Visitor_pattern pour l’arrière-plan.

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;
};

Puis pour chaque objet concret:

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.
}

puis définissez le visiteur de 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.
};

Ensuite, le visiteur concret qui sélectionne la fonction statique appropriée:

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.
};

enfin, utilisez-le:

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

Remarques:

  • La constance est partie comme un exercice.
  • Vous avez renvoyé une référence à partir d'un statique. À moins d'avoir un singleton, c'est discutable.

Si vous souhaitez éviter les erreurs de copier-coller dans lesquelles l'une de vos méthodes de visite appelle une fonction statique incorrecte, vous pouvez utiliser une fonction d'assistance modèle (qui ne peut pas être virtuelle) pour votre visiteur avec un modèle comme celui-ci:

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();
    }
};
0
Ben

Ce n'est pas possible, mais c'est simplement à cause d'une omission. Ce n'est pas quelque chose qui "n'a pas de sens" comme beaucoup de gens semblent le prétendre. Pour être clair, je parle de quelque chose comme ceci:

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.
}

C'est 100% quelque chose qui pourrait être mis en œuvre (ce n'est tout simplement pas le cas), et je dirais que quelque chose est utile.

Considérez le fonctionnement normal des fonctions virtuelles. Supprimez le statics et ajoutez-y d'autres éléments. Nous avons:

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();
}

Cela fonctionne bien et le compilateur crée deux tables, appelées VTables, et attribue des index aux fonctions virtuelles comme celle-ci.

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
};

Ensuite, chaque classe avec des fonctions virtuelles est complétée par un autre champ qui pointe vers sa VTable. Le compilateur les modifie ainsi:

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";
  }
};

Alors que se passe-t-il lorsque vous appelez b->sayMyName()? Fondamentalement ceci:

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

(Le premier paramètre devient this.)

Ok, alors comment cela fonctionnerait-il avec des fonctions virtuelles statiques? Quelle est la différence entre les fonctions membres statiques et non statiques? La seule différence est que ces derniers reçoivent un pointeur this.

Nous pouvons faire exactement la même chose avec les fonctions virtuelles statiques - il suffit de supprimer le pointeur this.

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

Cela pourrait alors supporter les deux syntaxes:

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

Alors ignorez tous les opposants. Cela est logique. Pourquoi n'est-il pas pris en charge alors? Je pense que c'est parce que cela a très peu d'avantages et pourrait même être un peu déroutant.

Le seul avantage technique par rapport à une fonction virtuelle normale est qu'il n'est pas nécessaire de passer this à la fonction, mais je ne pense pas que cela ferait une différence mesurable en termes de performances.

Cela signifie que vous n'avez pas de fonction statique et non statique distincte dans les cas où vous avez une instance et quand vous n'en avez pas, mais cela peut aussi être déroutant que ce ne soit vraiment que virtuel lorsque vous utilisez l'appel d'instance.

0
Timmmm

Avec c ++, vous pouvez utiliser l'héritage statique avec la méthode crt. Pour l'exemple, il est largement utilisé sur le modèle de fenêtre atl & wtl.

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

Pour être simple, vous avez une classe basée sur elle-même comme classe myclass: public myancestor. À partir de ce moment, la classe myancestor peut maintenant appeler votre fonction statique T :: YourImpl.

0
user10628663