webentwicklung-frage-antwort-db.com.de

C ++ statische virtuelle Mitglieder?

Ist es in C++ möglich, eine Mitgliedsfunktion zu haben, die sowohl static als auch virtual ist? Anscheinend gibt es keine einfache Möglichkeit, dies zu tun (static virtual member(); ist ein Kompilierungsfehler), aber gibt es zumindest eine Möglichkeit, den gleichen Effekt zu erzielen?

IE:

struct Object
{
     struct TypeInformation;

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

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

Es ist sinnvoll, GetTypeInformation() sowohl für eine Instanz (object->GetTypeInformation()) als auch für eine Klasse (SomeObject::GetTypeInformation()) zu verwenden, was für Vergleiche nützlich und für Vorlagen wichtig sein kann.

Ich kann mir nur vorstellen, zwei Funktionen/eine Funktion und eine Konstante pro Klasse zu schreiben oder Makros zu verwenden.

Irgendwelche anderen Lösungen?

130
cvb

Nein, es gibt keine Möglichkeit, da was passieren würde, wenn Sie Object::GetTypeInformation() aufrufen? Es kann nicht wissen, welche abgeleitete Klassenversion aufgerufen werden soll, da kein Objekt zugeordnet ist.

Sie müssen es zu einer nicht statischen virtuellen Funktion machen, um ordnungsgemäß zu funktionieren. Wenn Sie die Version einer bestimmten abgeleiteten Klasse auch ohne eine Objektinstanz nicht virtuell aufrufen möchten, müssen Sie auch eine zweite redundante statische nicht virtuelle Version bereitstellen.

73
Adam Rosenfield

Viele sagen, es ist nicht möglich, ich würde einen Schritt weiter gehen und sagen, es ist nicht sinnvoll.

Ein statischer Member ist etwas, das sich nicht auf eine Instanz bezieht, sondern nur auf die Klasse.

Ein virtuelles Mitglied ist etwas, das sich nicht direkt auf eine Klasse bezieht, sondern nur auf eine Instanz.

Ein statisches virtuelles Mitglied ist also etwas, das sich nicht auf eine Instanz oder Klasse bezieht.

55
Rasmus Kaj

Ich bin neulich auf dieses Problem gestoßen: Ich hatte einige Klassen voller statischer Methoden, aber ich wollte Vererbung und virtuelle Methoden verwenden und die Code-Wiederholung reduzieren. Meine Lösung war:

Verwenden Sie anstelle von statischen Methoden einen Singleton mit virtuellen Methoden.

Mit anderen Worten, jede Klasse sollte eine statische Methode enthalten, die Sie aufrufen, um einen Zeiger auf eine einzelne, gemeinsam genutzte Instanz der Klasse abzurufen. Sie können die wahren Konstruktoren als privat oder geschützt kennzeichnen, damit externer Code sie nicht durch das Erstellen zusätzlicher Instanzen missbrauchen kann.

In der Praxis ist die Verwendung eines Singletons der Verwendung statischer Methoden sehr ähnlich, mit der Ausnahme, dass Sie Vererbung und virtuelle Methoden nutzen können.

21
Nate C-K

Es ist möglich!

Aber was genau ist möglich, lassen Sie uns eingrenzen. Die Leute wollen oft eine Art "statische virtuelle Funktion", weil der Code dupliziert werden muss, um dieselbe Funktion durch den statischen Aufruf "SomeDerivedClass :: myfunction ()" und den polymorphen Aufruf "base_class_pointer-> myfunction ()" aufrufen zu können. Die "legale" Methode, um eine solche Funktionalität zuzulassen, ist das Duplizieren von Funktionsdefinitionen:

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

Was ist, wenn eine Basisklasse eine große Anzahl von statischen Funktionen hat und eine abgeleitete Klasse jede von ihnen überschreiben muss und man vergessen hat, eine doppelte Definition für die virtuelle Funktion bereitzustellen? Richtig, wir werden während Laufzeit einen seltsamen Fehler bekommen, der schwer aufzuspüren ist. Die Vervielfältigung von Code ist eine schlechte Sache. Das Folgende versucht, dieses Problem zu lösen (und ich möchte vorher sagen, dass es absolut typsicher ist und keine schwarze Magie wie Typid's oder Dynamic_cast's enthält :)

Daher möchten wir nur eine Definition von getTypeInformation () pro abgeleiteter Klasse bereitstellen, und es ist offensichtlich, dass es sich um eine Definition der Funktion static handeln muss, da "SomeDerivedClass :: getTypeInformation ( ) "wenn getTypeInformation () virtuell ist. Wie können wir die statische Funktion der abgeleiteten Klasse über den Zeiger auf die Basisklasse aufrufen? Mit vtable ist dies nicht möglich, da vtable nur Zeiger auf virtuelle Funktionen speichert. Da wir uns entschieden haben, keine virtuellen Funktionen zu verwenden, können wir vtable nicht zu unserem Vorteil ändern. Um dann über einen Zeiger auf die Basisklasse auf die statische Funktion der abgeleiteten Klasse zugreifen zu können, müssen wir den Typ eines Objekts in der Basisklasse speichern. Ein Ansatz besteht darin, die Basisklasse mithilfe eines "seltsam wiederkehrenden Schablonenmusters" templatisiert zu machen. Dies ist jedoch hier nicht angebracht, und wir verwenden eine Technik namens "Typlöschung":

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

Jetzt können wir den Typ eines Objekts in der Basisklasse "Object" mit einer Variablen "keeper" speichern:

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

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

};

In einer abgeleiteten Klasse muss der Keeper während der Erstellung initialisiert werden:

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

Lassen Sie uns syntaktischen Zucker hinzufügen:

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

Jetzt sehen die Erklärungen der Nachkommen so aus:

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

verwendung:

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

Vorteile:

  1. weniger Vervielfältigung von Code (aber wir müssen OVERRIDE_STATIC_FUNCTIONS in jedem Konstruktor aufrufen)

Nachteile:

  1. OVERRIDE_STATIC_FUNCTIONS in jedem Konstruktor
  2. speicher- und Leistungsaufwand
  3. erhöhte Komplexität

Offene Punkte:

1) gibt es unterschiedliche Bezeichnungen für statische und virtuelle Funktionen, wie man hier Unklarheiten löst?

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) wie man implizit OVERRIDE_STATIC_FUNCTIONS in jedem Konstruktor aufruft?

14
Alsk

Während Alsk bereits eine ziemlich ausführliche Antwort gegeben hat, möchte ich eine Alternative hinzufügen, da seine erweiterte Implementierung meiner Meinung nach zu kompliziert ist.

Wir beginnen mit einer abstrakten Basisklasse, die die Schnittstelle für alle Objekttypen bereitstellt:

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

Jetzt brauchen wir eine tatsächliche Implementierung. Um jedoch nicht sowohl die statischen als auch die virtuellen Methoden schreiben zu müssen, müssen unsere tatsächlichen Objektklassen die virtuellen Methoden erben. Dies funktioniert natürlich nur, wenn die Basisklasse weiß, wie sie auf die statische Member-Funktion zugreifen kann. Wir müssen also eine Vorlage verwenden und den tatsächlichen Objektklassennamen übergeben:

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

Schließlich müssen wir unsere realen Objekte implementieren. Hier müssen wir nur die statische Member-Funktion implementieren. Die virtuellen Member-Funktionen werden von der ObjectImpl-Vorlagenklasse geerbt, die mit dem Namen der abgeleiteten Klasse instanziiert ist, sodass sie auf ihre statischen Member zugreifen.

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

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

Fügen wir einen Code zum Testen hinzu:

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

Nachtrag (12. Januar 2019):

Anstatt die Funktion GetClassNameStatic () zu verwenden, können Sie den Klassennamen auch als statisches Element definieren, auch als "Inline", was in IIRC seit C++ 11 funktioniert (keine Angst vor allen Modifikatoren :)):

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

Es ist möglich. Machen Sie zwei Funktionen: statisch und virtuell

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

Nein, dies ist nicht möglich, da statischen Elementfunktionen ein Zeiger this fehlt. Und statische Member (sowohl Funktionen als auch Variablen) sind per se keine echten Klassenmitglieder. Sie werden nur zufällig von ClassName::member Aufgerufen und richten sich nach den Klassenzugriffsspezifizierern. Ihre Speicherung ist irgendwo außerhalb der Klasse definiert. Der Speicher wird nicht jedes Mal erstellt, wenn Sie ein Objekt der Klasse instanziieren. Zeiger auf Klassenmitglieder sind in Semantik und Syntax speziell. Ein Zeiger auf ein statisches Element ist in jeder Hinsicht ein normaler Zeiger.

virtuelle Funktionen in einer Klasse benötigen den Zeiger this und sind stark an die Klasse gekoppelt, daher können sie nicht statisch sein.

8
Mads Elvheim

Nun, eine recht späte Antwort, aber es ist möglich, das seltsamerweise wiederkehrende Vorlagenmuster zu verwenden. Dieser wikipedia Artikel enthält die Informationen, die Sie benötigen, und auch das Beispiel unter statischer Polymorphie ist das, wonach Sie gefragt werden.

6
tropicana

Nein, die statische Member-Funktion kann nicht virtuell sein, da das virtuelle Konzept zur Laufzeit mit Hilfe von vptr aufgelöst wird und vptr kein statisches Member einer Klasse ist nicht virtuell sein.

3
Prabhat Kumar

Ich denke, was Sie versuchen, kann durch Vorlagen erfolgen. Ich versuche hier zwischen den Zeilen zu lesen. Sie versuchen, eine Methode aus einem Code aufzurufen, in dem eine abgeleitete Version aufgerufen wird, der Aufrufer jedoch nicht angibt, welche Klasse. Beispiel:

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

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

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

int main()
{
    Try();
}

Sie möchten, dass Try () die Bar-Version von M aufruft, ohne Bar anzugeben. Die Art und Weise, wie Sie dies für die Statik tun, ist die Verwendung einer Vorlage. Also ändere es so:

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

Nein, dies ist nicht möglich, da statische Member zur Kompilierungszeit gebunden werden, während virtuelle Member zur Laufzeit gebunden werden.

0
PaulJWilliams

Erstens ist die Antwort richtig, dass das, was das OP anfordert, ein Widerspruch ist: Virtuelle Methoden hängen vom Laufzeit-Typ einer Instanz ab; Statische Funktionen hängen nicht von einer Instanz ab, sondern nur von einem Typ. Das heißt, es ist sinnvoll, statische Funktionen für einen bestimmten Typ etwas zurückgeben zu lassen. Ich hatte zum Beispiel eine Familie von MouseTool-Klassen für das Statusmuster und begann, jede mit einer statischen Funktion auszustatten, die den dazugehörigen Tastaturmodifikator zurückgibt. Ich habe diese statischen Funktionen in der Factory-Funktion verwendet, die die richtige MouseTool-Instanz erstellt haben. Diese Funktion hat den Mausstatus anhand von MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier () usw. überprüft und dann den entsprechenden instanziiert. Natürlich wollte ich später überprüfen, ob der Status richtig war, also wollte ich etwas schreiben wie "if (keyboardModifier == dynamic_type (* state) :: keyboardModifier ())" (keine echte C++ - Syntax), was diese Frage ist .

Wenn Sie dies wünschen, können Sie Ihre Lösung verfeinern. Trotzdem verstehe ich den Wunsch, statische Methoden zu haben und sie dann basierend auf dem dynamischen Typ einer Instanz dynamisch aufzurufen. Ich denke, das Besuchermuster kann Ihnen geben, was Sie wollen. Es gibt dir was du willst. Es ist ein bisschen zusätzlicher Code, aber er könnte für andere Besucher nützlich sein.

Siehe: http://en.wikipedia.org/wiki/Visitor_pattern für den Hintergrund.

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

Dann für jedes konkrete Objekt:

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

und definieren Sie dann den Basisbesucher:

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

Dann der konkrete Besucher, der die entsprechende statische Funktion auswählt:

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

zum Schluss benutze es:

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

Anmerkungen:

  • Konstanz bleibt als Übung.
  • Sie haben eine Referenz von einer statischen zurückgegeben. Wenn Sie keinen Singleton haben, ist das fraglich.

Wenn Sie Fehler beim Kopieren und Einfügen vermeiden möchten, bei denen eine Ihrer Besuchsmethoden die falsche statische Funktion aufruft, können Sie eine Hilfsfunktion (die selbst nicht virtuell sein kann) für Ihren Besucher mit einer Vorlage wie der folgenden verwenden:

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

Es ist nicht möglich, aber das liegt nur an einer Unterlassung. Es ist nichts, was "keinen Sinn ergibt", wie viele Leute zu behaupten scheinen. Um es klar zu sagen, ich spreche über so etwas:

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

Dies ist zu 100% etwas, das könnte implementiert werden kann (hat es einfach nicht), und ich würde etwas argumentieren, das nützlich ist.

Überlegen Sie, wie normale virtuelle Funktionen funktionieren. Entferne die statics und füge ein paar andere Sachen hinzu und wir haben:

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

Dies funktioniert einwandfrei und im Grunde genommen erstellt der Compiler zwei Tabellen mit dem Namen VTables und weist den virtuellen Funktionen wie folgt Indizes zu

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

Als nächstes wird jede Klasse mit virtuellen Funktionen um ein anderes Feld erweitert, das auf ihre VTable verweist. Der Compiler ändert sie also im Grunde so:

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

Was passiert dann eigentlich, wenn Sie b->sayMyName() aufrufen? Grundsätzlich gilt:

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

(Der erste Parameter wird zu this.)

Okay, gut, wie würde es mit statischen virtuellen Funktionen funktionieren? Was ist der Unterschied zwischen statischen und nicht statischen Elementfunktionen? Der einzige Unterschied besteht darin, dass letztere einen Zeiger this erhalten.

Mit statischen virtuellen Funktionen können wir genau dasselbe tun - entfernen Sie einfach den Zeiger this.

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

Dies könnte dann beide Syntaxen unterstützen:

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

Also ignoriere alle Neinsager. Es macht Sinn. Warum wird es dann nicht unterstützt? Ich denke, es ist, weil es sehr wenig Nutzen hat und sogar ein wenig verwirrend sein könnte.

Der einzige technische Vorteil gegenüber einer normalen virtuellen Funktion besteht darin, dass Sie this nicht an die Funktion übergeben müssen, aber ich denke nicht, dass dies einen messbaren Unterschied für die Leistung bedeuten würde.

Es bedeutet, dass Sie keine separate statische und nicht statische Funktion für Fälle haben, in denen Sie eine Instanz haben und wenn Sie keine Instanz haben, aber es kann auch verwirrend sein, dass es nur wirklich "virtuell" ist, wenn Sie es verwenden der Instanzaufruf.

0
Timmmm

Mit c ++ können Sie die statische Vererbung mit der CRT-Methode verwenden. Für das Beispiel wird es häufig bei Fenstervorlagen atl & wtl verwendet.

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

Einfach gesagt, Sie haben eine Klasse, die von sich aus wie die Klasse myclass behandelt wird: public myancestor. Ab diesem Punkt kann die myancestor-Klasse jetzt Ihre statische T :: YourImpl-Funktion aufrufen.

0
user10628663