Statyczne wirtualne elementy w C ++?


140

Czy w C ++ można mieć funkcję składową, która jest jednocześnie statici virtual? Wygląda na to, że nie ma na to prostego sposobu ( static virtual member();jest to błąd kompilacji), ale czy istnieje przynajmniej sposób na osiągnięcie tego samego efektu?

TO ZNACZY:

struct Object
{
     struct TypeInformation;

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

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

Sensowne jest używanie GetTypeInformation()zarówno instancji ( object->GetTypeInformation()), jak i klasy ( SomeObject::GetTypeInformation()), co może być przydatne do porównań i istotne dla szablonów.

Jedyne sposoby, które przychodzą mi do głowy, obejmują pisanie dwóch funkcji / funkcji i stałej, na klasę lub użycie makr.

Jakieś inne rozwiązania?


12
Tylko komentarz boczny: metody statyczne nie są wykonywane na żadnej instancji, co oznacza, że ​​nie mają niejawnego tego wskaźnika. Biorąc to pod uwagę, constsygnatura metody in a oznacza niejawny thiswskaźnik jako stały i nie może być stosowana do metod statycznych, ponieważ nie mają one niejawnego parametru.
David Rodríguez - dribeas

2
@cvb: Poważnie rozważę zastąpienie twojego przykładu kodem, który nie wymaga refleksji. Tak jest teraz, w pewnym sensie łączysz dwa oddzielne (choć powiązane) kwestie. Tak, i wiem, że minęło 5 i pół roku, odkąd o to zapytałeś.
einpoklum

Jedną z funkcji niejawnie wymaganej w tym miejscu jest sprawdzenie, czy kompilator sprawdza, czy każdy obiekt w hierarchii implementuje określony interfejs (w którym co najmniej jedna metoda jest statyczna). Zasadniczo czyste wirtualne sprawdzenie metody statycznej ma dużo sensu, ponieważ jeśli zapomnisz dodać metodę statyczną, kompilator powinien się pomylić. wirtualny nie jest tutaj słowem kluczowym, jest bardziej abstrakcyjny, co w C ++ jest rodzajem synonimów, z wyjątkiem tego konkretnego przypadku. Niestety obecnie nie możesz tego zrobić w C ++.
xryl669

Odpowiedzi:


75

Nie, nie da się tego zrobić, bo co się stanie, gdy zadzwonisz Object::GetTypeInformation()? Nie może wiedzieć, którą wersję klasy pochodnej ma wywołać, ponieważ nie ma z nią skojarzonego obiektu.

Będziesz musiał uczynić z niej niestatyczną funkcję wirtualną, aby działała poprawnie; jeśli chcesz mieć również możliwość nie wirtualnego wywoływania wersji określonej klasy pochodnej bez instancji obiektu, musisz również zapewnić drugą, nadmiarową, statyczną, niewirtualną wersję.


8
Jeśli myślisz o klasie statycznej (lub statycznych elementach składowych klas) jako o pojedynczej klasie, wszystko staje się oczywiste - w twoim przypadku należy po prostu wywołać Object :: GetTypeInformation - tak samo, jak wywołanie zwykłej metody wirtualnej na instancji klasy bazowej . (Oczywiście, jeśli C ++ obsługuje wirtualne metody statyczne)
Spook

13
To całkowicie zwodniczy argument. Jeśli użyjesz klasy zamiast obiektu, naturalnie użyje ona wersji z tej klasy, zamiast wykonywać wirtualną wysyłkę. Nic nowego.
Deduplikator

54

Wielu twierdzi, że nie jest to możliwe, poszedłbym o krok dalej i powiedziałbym, że nie ma to sensu.

Statyczny element członkowski to coś, co nie odnosi się do żadnej instancji, tylko do klasy.

Wirtualny element członkowski to coś, co nie odnosi się bezpośrednio do żadnej klasy, tylko do instancji.

Zatem statyczny wirtualny element członkowski byłby czymś, co nie odnosi się do żadnej instancji ani żadnej klasy.


42
Ma to doskonałe znaczenie w językach, w których klasy są wartościami pierwszorzędnymi - ma to np. Delphi, a także ma „statyczne wirtualne” metody.
Pavel Minaev

4
Dokładnie. „Funkcja wirtualna” to (z definicji) funkcja, która jest dynamicznie połączona , tj. Jest wybierana w czasie wykonywania w zależności od dynamicznego typu danego obiektu. Dlatego żaden obiekt = brak wirtualnego połączenia.
Kos

7
Myślę też, że statyczne wirtuale mają znaczenie. Możliwe byłoby zdefiniowanie klas interfejsów i uwzględnienie metod statycznych, które należy zaimplementować w klasie pochodnej.
bkausbk

34
Nie ma to większego znaczenia dla static virtualmetody, ale static czysta virtual metoda ma duże znaczenie w interfejsie.
Bret Kuhns

4
Posiadanie pliku static const string MyClassSillyAdditionalName.
einpoklum

23

Któregoś dnia napotkałem ten problem: miałem kilka klas pełnych metod statycznych, ale chciałem użyć dziedziczenia i metod wirtualnych oraz zredukować powtarzalność kodu. Moje rozwiązanie brzmiało:

Zamiast używać metod statycznych, użyj singletona z metodami wirtualnymi.

Innymi słowy, każda klasa powinna zawierać statyczną metodę, którą wywołujesz, aby uzyskać wskaźnik do pojedynczej, współużytkowanej instancji klasy. Możesz ustawić prawdziwe konstruktory jako prywatne lub chronione, aby kod zewnętrzny nie mógł ich niewłaściwie używać, tworząc dodatkowe instancje.

W praktyce używanie singletona jest bardzo podobne do używania metod statycznych, z tym wyjątkiem, że można skorzystać z dziedziczenia i metod wirtualnych.


Będzie to kosztowało mnie wydajność - chyba że kompilator może być pewien, że: 1. W rzeczywistości jest to singleton i 2. Nic z niego nie dziedziczy, nie sądzę, aby mógł zoptymalizować całość narzutu.
einpoklum

Jeśli martwi Cię wydajność tego rodzaju rzeczy, to prawdopodobnie C # jest dla Ciebie niewłaściwym językiem.
Nate CK

3
Ach, słuszna uwaga. Oczywiście minęło trochę czasu, odkąd o tym myślałem, odkąd napisałem to w 2009 roku. Pozwólcie więc, że przedstawię to w inny sposób: jeśli tego rodzaju wydajność Cię martwi, może powinieneś całkowicie unikać dziedziczenia. Plakat specjalnie prosił o metody wirtualne, więc dziwne jest, że przychodzisz tutaj, aby narzekać na narzuty związane z metodami wirtualnymi.
Nate CK

15

To jest możliwe!

Ale co dokładnie jest możliwe, zawęźmy. Ludzie często chcą jakiejś „statycznej funkcji wirtualnej” z powodu duplikowania kodu potrzebnego do wywołania tej samej funkcji poprzez statyczne wywołanie „SomeDerivedClass :: myfunction ()” i wywołanie polimorficzne „base_class_pointer-> myfunction ()”. „Legalną” metodą dopuszczenia takiej funkcjonalności jest powielanie definicji funkcji:

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

A co, jeśli klasa bazowa ma dużą liczbę funkcji statycznych, a klasa pochodna musi przesłonić każdą z nich, a jedna zapomniała podać powielającą się definicję funkcji wirtualnej. Dobrze, w czasie wykonywania pojawi się jakiś dziwny błąd który jest trudny do wyśledzenia. Bo powielanie kodu to zła rzecz. Poniższe próby próbują rozwiązać ten problem (i chcę wcześniej powiedzieć, że jest on całkowicie bezpieczny dla typów i nie zawiera żadnej czarnej magii, takiej jak typid czy dynamic_cast :)

Dlatego chcemy podać tylko jedną definicję metody getTypeInformation () na klasę pochodną i jest oczywiste, że musi to być definicja statycznafunkcja, ponieważ nie jest możliwe wywołanie „SomeDerivedClass :: getTypeInformation ()”, jeśli getTypeInformation () jest wirtualne. Jak możemy wywołać funkcję statyczną klasy pochodnej poprzez wskaźnik do klasy bazowej? W przypadku vtable nie jest to możliwe, ponieważ vtable przechowuje wskaźniki tylko do funkcji wirtualnych, a ponieważ zdecydowaliśmy się nie używać funkcji wirtualnych, nie możemy modyfikować vtable na naszą korzyść. Następnie, aby móc uzyskać dostęp do funkcji statycznej dla klasy pochodnej poprzez wskaźnik do klasy bazowej, musimy w jakiś sposób przechowywać typ obiektu w jego klasie bazowej. Jedną z metod jest stworzenie szablonu klasy bazowej za pomocą „ciekawie powtarzającego się wzorca szablonu”, ale nie jest to odpowiednie w tym przypadku i użyjemy techniki zwanej „wymazywaniem typów”:

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

Teraz możemy przechowywać typ obiektu w klasie bazowej „Object” ze zmienną „Keeper”:

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

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

};

W klasie pochodnej opiekun klasy musi zostać zainicjowany podczas konstruowania:

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

Dodajmy cukier syntaktyczny:

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

Teraz deklaracje potomków wyglądają następująco:

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

stosowanie:

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

Zalety:

  1. mniejsze powielanie kodu (ale musimy wywołać OVERRIDE_STATIC_FUNCTIONS w każdym konstruktorze)

Niedogodności:

  1. OVERRIDE_STATIC_FUNCTIONS w każdym konstruktorze
  2. narzut pamięci i wydajności
  3. zwiększona złożoność

Otwarte kwestie:

1) Istnieją różne nazwy funkcji statycznych i wirtualnych, jak rozwiązać tutaj niejednoznaczność?

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) jak niejawnie wywołać OVERRIDE_STATIC_FUNCTIONS wewnątrz każdego konstruktora?


+1 za wysiłek, chociaż nie jestem pewien, czy jest to bardziej eleganckie niż zwykłe delegowanie funkcji do singletona za pomocą metod wirtualnych.
einpoklum

1
@einpoklum, przychodzi mi do głowy sytuacja, w której może to być lepsze. Załóżmy, że mamy dużo kodu klienta, który już wywołuje metody statyczne. Przejście z metod statycznych na singleton metodami wirtualnymi wymagałoby zmian w kodzie klienta, natomiast rozwiązanie przedstawione powyżej jest nieinwazyjne.
Alsk

Słowo kluczowe „virtual” nie jest wymagane dla „Foo :: getTypeInformation” i „TypeKeeperImpl :: getTypeInformation”.
bartolo-otrit

12

Chociaż Alsk udzielił już dość szczegółowej odpowiedzi, chciałbym dodać alternatywę, ponieważ uważam, że jego ulepszona implementacja jest zbyt skomplikowana.

Zaczynamy od abstrakcyjnej klasy bazowej, która zapewnia interfejs dla wszystkich typów obiektów:

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

Teraz potrzebujemy rzeczywistej implementacji. Ale aby uniknąć konieczności pisania zarówno metod statycznych, jak i wirtualnych, będziemy mieć nasze rzeczywiste klasy obiektów dziedziczące metody wirtualne. Działa to oczywiście tylko wtedy, gdy klasa bazowa wie, jak uzyskać dostęp do statycznej funkcji składowej. Musimy więc użyć szablonu i przekazać mu rzeczywistą nazwę klasy obiektów:

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

Wreszcie musimy zaimplementować nasz prawdziwy obiekt (y). Tutaj musimy tylko zaimplementować statyczną funkcję składową, wirtualne funkcje składowe zostaną odziedziczone z klasy szablonu ObjectImpl, utworzona z nazwą klasy pochodnej, dzięki czemu uzyska dostęp do jej statycznych składowych.

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

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

Dodajmy kod do przetestowania:

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

Dodatek (12 stycznia 2019 r.):

Zamiast korzystać z funkcji GetClassNameStatic (), możesz również zdefiniować nazwę klasy jako statyczny element członkowski, nawet „inline”, który IIRC działa od C ++ 11 (nie przejmuj się wszystkimi modyfikatorami :)):

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

    // ...
};

11

To jest możliwe. Twórz dwie funkcje: statyczną i wirtualną

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

4
Ponadto metody statyczne nie mogą być const. To po prostu nie ma sensu, w jakim przypadku nie zamierzają mutować?
David Rodríguez - dribeas

1
Jest to głównie powielanie kodu. Pomysł polega na tym, że podklasy muszą mieć tylko statyczny element członkowski stałej, a nie muszą mieć dostępu do kodu.
einpoklum

8

Nie, nie jest to możliwe, ponieważ statyczne funkcje składowe nie mają thiswskaźnika. A statyczne elementy składowe (zarówno funkcje, jak i zmienne) nie są tak naprawdę składnikami klas jako takimi. Tak się składa, że ​​są przywoływane przezClassName::member i stosują się do specyfikatorów dostępu do klas. Ich przechowywanie jest zdefiniowane gdzieś poza klasą; magazyn nie jest tworzony za każdym razem, gdy tworzysz instancję obiektu klasy. Wskaźniki do członków klas mają szczególne znaczenie w semantyce i składni. Wskaźnik do statycznego elementu członkowskiego jest normalnym wskaźnikiem pod każdym względem.

funkcje wirtualne w klasie potrzebują thiswskaźnika i są bardzo powiązane z klasą, dlatego nie mogą być statyczne.


1
Tylko funkcje niestatyczne wymagają this wskaźnika. funkcje statyczne nie są specyficzne dla instancji i nie będą ich potrzebować. Tak więc - to nie jest powód, dla którego wirtualne statyczne elementy członkowskie są niemożliwe.
einpoklum

7

Cóż, dość późna odpowiedź, ale jest to możliwe przy użyciu ciekawie powtarzającego się wzorca szablonu. Ten artykuł na Wikipedii zawiera potrzebne informacje, a także przykład pod statycznym polimorfizmem jest tym, o co Cię proszą.


3

Myślę, że to, co próbujesz zrobić, można zrobić za pomocą szablonów. Próbuję czytać między wierszami. To, co próbujesz zrobić, to wywołanie metody z jakiegoś kodu, w którym wywołuje ona wersję pochodną, ​​ale obiekt wywołujący nie określa, która klasa. Przykład:

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

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

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

int main()
{
    Try();
}

Chcesz, aby Try () wywoływał wersję Bar M bez określania Bar. Sposób, w jaki robisz to w przypadku statyki, polega na użyciu szablonu. Więc zmień to tak:

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

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

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

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

1
Jeśli wciśniesz swój kod o 4 spacje, możesz go automatycznie sformatować. Alternatywnie uważam, że możesz użyć back tick, aby osiągnąć ten sam cel w linii.
chollida

1
To oczywiste, że przegapiłem. Dziękuję Ci. Mimo to członkowie łonu są dziwni.
allesblinkt

M () nie jest funkcją statyczną. jak to się nazywa T :: M ()?
DDukDDak99

3

Nie, statyczna funkcja członkowska nie może być wirtualna. Ponieważ koncepcja wirtualna jest rozwiązywana w czasie wykonywania za pomocą vptr, a vptr nie jest statycznym elementem klasy. Z powodu tej statycznej funkcji składowej nie można uzyskać dostępu do vptr, więc statyczny element członkowski może nie być wirtualnym.


2
Tylko metody wirtualne specyficzne dla instancji wymagają vtable instancji. Możesz mieć statyczny - jeden na klasę - vtable. A jeśli chcesz wiedzieć o instancjach, po prostu wskaż z tabeli vtable instancji również na klasę statics vtable.
einpoklum

2

Nie jest to możliwe, ale to tylko z powodu zaniedbania. To nie jest coś, co „nie ma sensu”, jak twierdzi wielu ludzi. Żeby było jasne, mówię o czymś takim:

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

Jest to w 100% coś, co można zaimplementować (po prostu nie ma) i argumentowałbym, że jest to przydatne.

Zastanów się, jak działają normalne funkcje wirtualne. Usuń statics i dodaj kilka innych rzeczy i mamy:

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

Działa to dobrze iw zasadzie kompilator tworzy dwie tabele zwane tabelami VTables i przypisuje indeksy do takich funkcji wirtualnych

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

Następnie każda klasa z funkcjami wirtualnymi jest rozszerzana o inne pole wskazujące na jej VTable, więc kompilator zasadniczo zmienia je tak, aby wyglądały następująco:

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

Więc co się właściwie dzieje, kiedy dzwonisz b->sayMyName()? Zasadniczo to:

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

(Pierwszy parametr zmieni się na this.)

Dobrze, więc jak by to działało ze statycznymi funkcjami wirtualnymi? Jaka jest różnica między statycznymi i niestatycznymi funkcjami składowymi? Jedyną różnicą jest to, że te ostatnie otrzymują thiswskaźnik.

Dokładnie to samo możemy zrobić ze statycznymi funkcjami wirtualnymi - wystarczy usunąć thiswskaźnik.

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

To mogłoby wtedy obsługiwać obie składnie:

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

Więc zignoruj ​​wszystkich przeciwników. To ma sens. Dlaczego więc nie jest obsługiwany? Myślę, że to dlatego, że ma bardzo małe korzyści, a nawet może być trochę zagmatwane.

Jedyną przewagą techniczną w porównaniu ze zwykłą funkcją wirtualną jest to, że nie trzeba przechodzić thisdo funkcji, ale nie sądzę, aby miało to jakikolwiek wymierny wpływ na wydajność.

Oznacza to, że nie masz oddzielnej funkcji statycznej i niestatycznej w przypadkach, gdy masz instancję i nie masz instancji, ale może być również mylące, że jest ona naprawdę „wirtualna” tylko wtedy, gdy używasz wywołanie instancji.


0

Nie, nie jest to możliwe, ponieważ statyczne elementy członkowskie są powiązane w czasie kompilacji, podczas gdy wirtualne elementy członkowskie są powiązane w czasie wykonywania.


0

Po pierwsze, odpowiedzi są prawidłowe, że to, czego żąda OP, jest sprzecznością pod względem: metody wirtualne zależą od typu instancji w czasie wykonywania; Funkcje statyczne w szczególności nie zależą od instancji - tylko od typu. To powiedziawszy, sensowne jest, aby funkcje statyczne zwracały coś specyficznego dla typu. Na przykład, miałem rodzinę klas MouseTool dla wzorca State i zacząłem mieć każdą z nich funkcję statyczną zwracającą modyfikator klawiatury, który był z nią powiązany; Użyłem tych statycznych funkcji w funkcji fabrycznej, która utworzyła poprawną instancję MouseTool. Ta funkcja sprawdziła stan myszy względem MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier () itp., A następnie utworzyła odpowiedni. Oczywiście później chciałem sprawdzić, czy stan jest właściwy, więc chciałem napisać coś w rodzaju „

Jeśli więc chcesz tego, możesz zmienić swoje rozwiązanie. Mimo to rozumiem pragnienie posiadania statycznych metod, a następnie wywołuję je dynamicznie w oparciu o dynamiczny typ instancji. Myślę, że wzorzec gości może dać ci to, czego chcesz. Daje ci to, czego chcesz. To trochę dodatkowego kodu, ale może być przydatny dla innych odwiedzających.

Więcej informacji można znaleźć pod adresem : http://en.wikipedia.org/wiki/Visitor_pattern .

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

Następnie dla każdego konkretnego obiektu:

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

a następnie zdefiniuj gościa podstawowego:

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

Następnie konkretny gość, który wybiera odpowiednią funkcję statyczną:

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

na koniec użyj go:

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

Uwagi:

  • Stałość pozostawiona jako ćwiczenie.
  • Zwróciłeś odwołanie ze statycznego. Jeśli nie masz singletona, to wątpliwe.

Jeśli chcesz uniknąć błędów kopiuj-wklej, w przypadku których jedna z metod odwiedzania wywołuje niewłaściwą funkcję statyczną, możesz użyć funkcji pomocniczej opartej na szablonie (która sama w sobie nie może być wirtualna) t gościa z szablonem takim jak ten:

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

wirtualne metody statyczne, gdyby istniały, nie zależałyby od niczego w instancji - ale instancja musiałaby znać jej typ, aby je wywołać. Może to zostać wypracowane przez kompilator (np. Używając pewnej pojedynczej struktury danych dla poszczególnych klas ze wskaźnikami do wirtualnych metod statycznych i składowych). Z pewnością nie jest to sprzeczność terminowa.
einpoklum

To, czy jest to sprzeczność terminowa, czy nie, jest kwestią semantyki. Można by sobie wyobrazić C ++ pozwalający na wywoływanie statystyk z instancji (np. Foo foo; ... foo::bar();Zamiast Foo::bar();). Nie jest inaczej, decltype(foo)::bar();ale to znowu byłoby statycznie związane. Podejście gościa wydaje się rozsądnym sposobem uzyskania tego zachowania bez robienia statycznej metody jako wirtualnej metody stałej.
Ben

0

W języku c ++ możesz używać dziedziczenia statycznego z metodą crt. Na przykład jest szeroko stosowany w szablonie okna atl & wtl.

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

Mówiąc prościej, masz klasę, która jest tworzona z samego siebie, podobnie jak klasa myclass: public myancestor. Od tego momentu klasa myancestor może teraz wywołać twoją statyczną funkcję T :: YourImpl.


-1

Może możesz wypróbować moje rozwiązanie poniżej:

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

Tak, wiem, 4 lata. Wyjaśnienie -score dla tych, którzy nie chcą czytać kodu w tak wielu szczegółach. Base::mSelfodnosi się do OSTATNIO skonstruowanej instancji dowolnej klasy pochodnej, nawet jeśli ta instancja została zniszczona . więc 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 */co NIE jest tym, co jest potrzebne.
Jesse Chisholm,

-3

Jak powiedzieli inni, istnieją 2 ważne informacje:

  1. nie ma thiswskaźnika podczas wykonywania statycznego wywołania funkcji i
  2. te thispunkty wskaźnik do struktury, gdzie tabela wirtualny lub thunk, są wykorzystywane do wyszukiwania, która metoda Runtime zadzwonić.

Funkcja statyczna jest określana w czasie kompilacji.

Pokazałem ten przykład kodu w statycznych elementach członkowskich C ++ w klasie ; pokazuje, że można wywołać metodę statyczną, mając wskaźnik zerowy:

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

6
Technicznie jest to niezdefiniowane zachowanie. Z żadnego powodu nie można szanować pustego wskaźnika. Jedyne, co możesz zrobić ze wskaźnikiem zerowym, to a) przypisać do niego inny wskaźnik ib) porównać go z innym wskaźnikiem.
KeithB

1
Co więcej, można to porównać tylko pod kątem równości (lub nierówności_ z innym wskaźnikiem, a nie kolejnością. Tj., Itp. p < null, p >= nullWszystkie są również niezdefiniowane.
Pavel Minaev

1
@KeithB - ​​Aby uzyskać kompletność, możesz również bezpiecznie wywołać usuwanie po pustym wskaźniku.
Steve Rowe
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.