C ++ anggota virtual statis?


140

Apakah mungkin dalam C ++ untuk memiliki fungsi anggota yang keduanya staticdan virtual? Rupanya, tidak ada cara langsung untuk melakukannya (static virtual member(); adalah kesalahan kompilasi), tetapi apakah ada setidaknya cara untuk mencapai efek yang sama?

YAITU:

struct Object
{
     struct TypeInformation;

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

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

Masuk akal untuk menggunakan GetTypeInformation()keduanya pada instance ( object->GetTypeInformation()) dan pada kelas (SomeObject::GetTypeInformation() ), yang dapat berguna untuk perbandingan dan penting untuk template.

Satu-satunya cara yang dapat saya pikirkan adalah menulis dua fungsi / fungsi dan konstanta, per kelas, atau menggunakan makro.

Ada solusi lain?


12
Hanya komentar sampingan: metode statis tidak mengeksekusi pada contoh apa pun, apa artinya bahwa mereka tidak memiliki pointer ini secara implisit. Yang sedang berkata, constdalam metode tanda tangan menandai thispointer implisit sebagai konstan dan tidak dapat diterapkan pada metode statis karena mereka tidak memiliki parameter implisit.
David Rodríguez - dribeas

2
@ cvb: Saya serius akan mempertimbangkan untuk mengganti contoh Anda dengan kode yang tidak melibatkan refleksi. Cara sekarang Anda semacam menggabungkan dua masalah yang terpisah (meskipun terkait). Ya, dan saya tahu ini 5 setengah tahun sejak Anda menanyakannya.
einpoklum

Salah satu fitur yang diperlukan secara implisit di sini adalah untuk memiliki kompilator memeriksa bahwa setiap objek dalam hierarki mengimplementasikan antarmuka tertentu (di mana satu atau lebih metode statis). Pada dasarnya, pemeriksaan virtual murni untuk metode statis sangat masuk akal, karena jika Anda lupa untuk menambahkan metode statis, maka kompiler akan kesalahan. virtual bukanlah kata kunci di sini, ini lebih abstrak yang kebetulan sejenis sinonim dalam C ++, kecuali untuk kasus khusus ini. Sayangnya, Anda tidak dapat melakukannya dengan C ++ saat ini.
xryl669

Jawaban:


75

Tidak, tidak ada cara untuk melakukannya, karena apa yang akan terjadi ketika Anda menelepon Object::GetTypeInformation()? Itu tidak bisa tahu versi kelas turunan mana untuk memanggil karena tidak ada objek yang terkait dengannya.

Anda harus menjadikannya fungsi virtual non-statis agar berfungsi dengan baik; jika Anda juga ingin dapat memanggil versi kelas turunan tertentu secara non-virtual tanpa instance objek, Anda juga harus menyediakan versi non-virtual statis redundan kedua.


8
Jika Anda menganggap kelas statis (atau kelas anggota statis) sebagai singleton, semuanya menjadi jelas - dalam kasus Anda cukup Object :: GetTypeInformation harus dipanggil - cara yang sama dengan memanggil metode virtual biasa pada instance kelas dasar . (Tentu saja, jika C ++ mendukung metode statis virtual)
Spook

13
Itu argumen yang sepenuhnya spekulatif. Jika Anda menggunakan kelas alih-alih objek, secara alami akan menggunakan versi dari kelas itu, alih-alih melakukan pengiriman virtual. Tidak ada yang baru di sana.
Deduplicator

54

Banyak yang mengatakan itu tidak mungkin, saya akan melangkah lebih jauh dan mengatakan itu tidak berarti.

Anggota statis adalah sesuatu yang tidak berhubungan dengan instance apa pun, hanya untuk kelas.

Anggota virtual adalah sesuatu yang tidak berhubungan langsung dengan kelas mana pun, hanya dengan sebuah instance.

Jadi anggota virtual statis akan menjadi sesuatu yang tidak berhubungan dengan instance atau kelas apa pun.


42
Ini sangat berarti dalam bahasa di mana kelas adalah nilai kelas satu - misalnya Delphi memilikinya, dan juga memiliki metode "virtual virtual".
Pavel Minaev

4
Persis. "Fungsi virtual" adalah (menurut definisi) fungsi yang terhubung secara dinamis , yaitu dipilih pada saat runtime tergantung pada tipe dinamis dari objek yang diberikan. Karenanya, tidak ada objek = tidak ada panggilan virtual.
Kos

7
Saya juga berpikir bahwa virtual statis itu bermakna. Dimungkinkan untuk mendefinisikan kelas antarmuka dan menyertakan metode statis yang harus diimplementasikan di kelas turunan.
bkausbk

34
Ini tidak begitu berarti untuk suatu static virtualmetode, tetapi metode static murni virtual sangat berarti dalam suatu antarmuka.
Bret Kuhns

4
Sangatlah bermakna memiliki static const string MyClassSillyAdditionalName.
einpoklum

23

Saya mengalami masalah ini beberapa hari yang lalu: Saya memiliki beberapa kelas yang penuh dengan metode statis tetapi saya ingin menggunakan metode pewarisan dan virtual dan mengurangi pengulangan kode. Solusi saya adalah:

Alih-alih menggunakan metode statis, gunakan singleton dengan metode virtual.

Dengan kata lain, setiap kelas harus berisi metode statis yang Anda panggil untuk mendapatkan pointer ke instance kelas yang dibagi bersama. Anda dapat menjadikan konstruktor yang sebenarnya privat atau terlindungi sehingga kode luar tidak dapat menyalahgunakannya dengan membuat instance tambahan.

Dalam praktiknya, menggunakan singleton sangat mirip dengan menggunakan metode statis kecuali bahwa Anda dapat mengambil keuntungan dari metode pewarisan dan virtual.


Itu akan merugikan kinerja saya - kecuali jika kompiler dapat memastikan bahwa: 1. Ini sebenarnya singleton dan 2. Tidak ada yang mewarisi darinya, saya tidak berpikir itu dapat mengoptimalkan semua overhead.
einpoklum

Jika kinerja hal semacam ini membuat Anda khawatir maka C # mungkin bahasa yang salah untuk Anda.
Nate CK

3
Ah, poin bagus. Jelas sudah agak lama sejak saya memikirkan hal ini sejak saya menulisnya pada tahun 2009. Biarkan saya mengatakannya dengan cara lain, lalu: jika hal kinerja semacam ini membuat Anda khawatir, mungkin Anda harus menghindari penggunaan warisan sepenuhnya. Poster khusus meminta metode virtual, jadi aneh bahwa Anda datang ke sini untuk mengeluh tentang overhead metode virtual.
Nate CK

15

Itu mungkin!

Tapi apa sebenarnya yang mungkin, mari kita persempit. Orang sering menginginkan semacam "fungsi virtual statis" karena duplikasi kode yang diperlukan untuk dapat memanggil fungsi yang sama melalui panggilan statis "SomeDerivedClass :: myfunction ()" dan panggilan polimorf "base_class_pointer-> myfunction ()". Metode "Legal" untuk mengizinkan fungsi seperti itu adalah duplikasi definisi fungsi:

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

Bagaimana jika kelas dasar memiliki sejumlah besar fungsi statis dan kelas turunan harus menimpanya masing-masing dan orang lupa untuk memberikan definisi duplikat untuk fungsi virtual. Benar, kami akan mendapatkan beberapa kesalahan aneh saat runtime yang sulit dilacak. Menyebabkan duplikasi kode adalah hal yang buruk. Berikut ini mencoba untuk menyelesaikan masalah ini (dan saya ingin memberitahu sebelumnya bahwa itu benar-benar aman dan tidak mengandung ilmu hitam seperti typeid atau dynamic_cast :)

Jadi, kami ingin memberikan hanya satu definisi getTypeInformation () per kelas turunan dan jelas bahwa itu harus merupakan definisi dari statisberfungsi karena tidak mungkin untuk memanggil "SomeDerivedClass :: getTypeInformation ()" jika getTypeInformation () adalah virtual. Bagaimana kita bisa memanggil fungsi statis dari kelas turunan melalui pointer ke kelas dasar? Tidak mungkin dengan vtable karena vtable hanya menyimpan pointer ke fungsi virtual dan karena kami memutuskan untuk tidak menggunakan fungsi virtual, kami tidak dapat memodifikasi vtable untuk keuntungan kami. Kemudian, untuk dapat mengakses fungsi statis untuk kelas turunan melalui pointer ke kelas dasar kita harus menyimpan entah bagaimana jenis objek di dalam kelas dasarnya. Salah satu pendekatannya adalah membuat kelas dasar di-templatized menggunakan "pola templat berulang-ulang yang anehnya" tetapi tidak sesuai di sini dan kami akan menggunakan teknik yang disebut "type erasure":

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

Sekarang kita dapat menyimpan jenis objek dalam kelas dasar "Object" dengan variabel "keeper":

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

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

};

Dalam penjaga kelas turunan harus diinisialisasi selama konstruksi:

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

Mari kita tambahkan gula sintaksis:

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

Sekarang deklarasi keturunan terlihat seperti:

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

pemakaian:

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

Keuntungan:

  1. lebih sedikit duplikasi kode (tetapi kami harus memanggil OVERRIDE_STATIC_FUNCTIONS di setiap konstruktor)

Kekurangan:

  1. OVERRIDE_STATIC_FUNCTIONS di setiap konstruktor
  2. memori dan overhead kinerja
  3. peningkatan kompleksitas

Masalah terbuka:

1) ada berbagai nama untuk fungsi statis dan virtual cara mengatasi ambiguitas di sini?

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) bagaimana cara memanggil OVERRIDE_STATIC_FUNCTIONS secara implisit di dalam setiap konstruktor?


Memberi +1 untuk upaya, meskipun saya tidak yakin ini lebih elegan daripada hanya mendelegasikan fungsi ke singleton dengan metode virtual.
einpoklum

1
@einpoklum, saya bisa memikirkan situasi saat ini bisa lebih disukai. Misalkan kita memiliki banyak kode klien yang sudah memanggil metode statis. Beralih dari metode statis ke singleton dengan metode virtual akan memerlukan perubahan dalam kode klien sementara solusi yang disajikan di atas adalah non-invasif.
Alsk

Kata kunci "virtual" tidak diperlukan untuk "Foo :: getTypeInformation" dan "TypeKeeperImpl :: getTypeInformation".
bartolo-otrit

12

Sementara Alsk telah memberikan jawaban yang cukup terperinci, saya ingin menambahkan alternatif, karena saya pikir penerapannya yang disempurnakan terlalu rumit.

Kita mulai dengan kelas dasar abstrak, yang menyediakan antarmuka untuk semua jenis objek:

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

Sekarang kita membutuhkan implementasi yang sebenarnya. Tetapi untuk menghindari keharusan menulis metode statis dan virtual, kita akan meminta kelas objek aktual kita mewarisi metode virtual. Ini jelas hanya berfungsi, jika kelas dasar tahu bagaimana mengakses fungsi anggota statis. Jadi kita perlu menggunakan templat dan meneruskan nama kelas objek aktual ke sana:

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

Akhirnya kita perlu mengimplementasikan objek nyata kita. Di sini kita hanya perlu mengimplementasikan fungsi anggota statis, fungsi anggota virtual akan diwarisi dari kelas templat ObjectImpl, dipakai dengan nama kelas turunan, sehingga akan mengakses anggota statis itu.

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

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

Mari tambahkan beberapa kode untuk diuji:

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

Tambahan (12 Januari 2019):

Alih-alih menggunakan fungsi GetClassNameStatic (), Anda juga dapat mendefinisikan nama kelas sebagai anggota statis, bahkan "inline", yang bekerja IIRC sejak C ++ 11 (jangan takut oleh semua pengubah :)):

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

Itu mungkin. Buat dua fungsi: statis dan 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
};

4
Juga, metode statis tidak bisa menjadi const. Itu tidak masuk akal, contoh apa yang tidak akan mereka mutasikan?
David Rodríguez - dribeas

1
Ini sebagian besar hanya duplikasi kode. Idenya adalah untuk subclass hanya perlu memiliki anggota const statis, tidak harus memiliki kode mengaksesnya.
einpoklum

8

Tidak, ini tidak mungkin, karena fungsi anggota statis tidak memiliki thispointer. Dan anggota statis (baik fungsi dan variabel) tidak benar-benar anggota kelas per-se. Mereka kebetulan dipanggil oleh ClassName::member, dan mematuhi penentu akses kelas. Penyimpanan mereka didefinisikan di suatu tempat di luar kelas; penyimpanan tidak dibuat setiap kali Anda membuat objek kelas. Pointer ke anggota kelas khusus dalam semantik dan sintaksis. Pointer ke anggota statis adalah pointer normal dalam semua hal.

fungsi virtual di kelas membutuhkan thispointer, dan sangat digabungkan ke kelas, karenanya mereka tidak bisa statis.


1
Hanya fungsi non-statis yang membutuhkan this pointer. fungsi statis tidak spesifik untuk sebuah instance, dan tidak memerlukannya. Jadi - itu bukan alasan anggota statis virtual tidak mungkin.
einpoklum

7

Yah, jawaban yang agak terlambat tetapi mungkin menggunakan pola templat berulang yang aneh. Ini wikipedia artikel memiliki info yang Anda butuhkan dan juga contoh di bawah statis polimorfisme adalah apa yang Anda minta.


3

Saya pikir apa yang Anda coba lakukan dapat dilakukan melalui template. Saya mencoba membaca yang tersirat di sini. Apa yang Anda coba lakukan adalah memanggil metode dari beberapa kode, di mana ia memanggil versi turunan tetapi pemanggil tidak menentukan kelas mana. Contoh:

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

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

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

int main()
{
    Try();
}

Anda ingin Try () memanggil versi Bar M tanpa menentukan Bar. Cara Anda melakukannya untuk statika adalah dengan menggunakan templat. Jadi ubah seperti ini:

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

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

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

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

1
Jika Anda membuat indentasi kode 4 spasi, Anda bisa memformatnya secara otomatis. Atau saya yakin Anda dapat menggunakan centang kembali untuk mencapai tujuan yang sama sebaris.
chollida

1
Ini jelas yang saya lewatkan. Terima kasih. Tetap saja, anggota pubis itu aneh.
allesblinkt

M () bukan fungsi statis. bagaimana ini disebut T :: M ()?
DDukDDak99

3

Tidak, fungsi anggota statis tidak boleh virtual. Karena konsep virtual diselesaikan pada saat dijalankan dengan bantuan vptr, dan vptr bukan anggota statis suatu kelas. Karena fungsi anggota statis tidak dapat mengakses vptr sehingga anggota statis dapat menjadi virtual.


2
Hanya metode virtual khusus-instance yang memerlukan instance instance. Anda dapat memiliki vtable statis - satu per kelas -. Dan jika Anda ingin instance mengetahuinya, tunjukkan saja dari instance vtable ke vtable statika kelas.
einpoklum

2

Itu tidak mungkin, tapi itu hanya karena kelalaian. Itu bukan sesuatu yang "tidak masuk akal" karena banyak orang tampaknya mengklaim. Untuk lebih jelasnya, saya berbicara tentang sesuatu seperti ini:

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

Ini adalah 100% sesuatu yang dapat diimplementasikan (hanya saja belum), dan saya berpendapat sesuatu yang berguna.

Pertimbangkan cara kerja fungsi virtual normal. Hapus statics dan tambahkan beberapa hal lain dan kami punya:

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

Ini berfungsi dengan baik dan pada dasarnya yang terjadi adalah kompiler membuat dua tabel, yang disebut VTables, dan memberikan indeks ke fungsi virtual seperti ini

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

Selanjutnya setiap kelas dengan fungsi virtual ditambah dengan bidang lain yang menunjuk ke VTable-nya, sehingga kompiler pada dasarnya mengubahnya menjadi seperti ini:

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

Lalu apa yang sebenarnya terjadi ketika Anda menelepon b->sayMyName()? Pada dasarnya ini:

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

(Parameter pertama menjadi this.)

Oke, jadi bagaimana cara kerjanya dengan fungsi virtual statis? Nah apa perbedaan antara fungsi anggota statis dan non-statis? Satu-satunya perbedaan adalah bahwa yang terakhir mendapatkan thispointer.

Kita dapat melakukan hal yang persis sama dengan fungsi virtual statis - cukup hapus thispointer.

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

Ini kemudian dapat mendukung kedua sintaks:

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

Jadi abaikan semua penentang. Ini tidak masuk akal. Mengapa itu tidak didukung? Saya pikir itu karena manfaatnya sangat kecil dan bahkan bisa sedikit membingungkan.

Satu-satunya keunggulan teknis atas fungsi virtual normal adalah Anda tidak perlu beralih thiske fungsi tersebut, tetapi saya tidak berpikir itu akan membuat perbedaan yang terukur untuk kinerja.

Itu berarti Anda tidak memiliki fungsi statis dan non-statis yang terpisah untuk kasus ketika Anda memiliki instance, dan ketika Anda tidak memiliki instance, tetapi juga mungkin membingungkan bahwa itu hanya benar-benar "virtual" ketika Anda menggunakan panggilan instan.


0

Tidak, itu tidak mungkin, karena anggota statis terikat pada waktu kompilasi, sementara anggota virtual terikat pada saat runtime.


0

Pertama, balasannya benar bahwa yang diminta OP adalah kontradiksi dalam hal: metode virtual bergantung pada jenis run-time dari sebuah instance; fungsi statis secara khusus tidak bergantung pada instance - hanya pada tipe. Yang mengatakan, masuk akal untuk memiliki fungsi statis mengembalikan sesuatu yang spesifik untuk suatu tipe. Sebagai contoh, saya memiliki keluarga kelas MouseTool untuk pola Negara dan saya mulai memiliki masing-masing memiliki fungsi statis mengembalikan pengubah keyboard yang menyertainya; Saya menggunakan fungsi-fungsi statis di fungsi pabrik yang membuat instance MouseTool yang benar. Fungsi itu memeriksa keadaan mouse terhadap MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier (), dll. Dan kemudian instantiated yang sesuai. Tentu saja kemudian saya ingin memeriksa apakah keadaannya benar jadi saya ingin menulis sesuatu seperti "

Jadi, jika Anda mendapati diri Anda menginginkan ini, Anda mungkin ingin mengulang solusi Anda. Namun, saya mengerti keinginan untuk memiliki metode statis dan kemudian menyebutnya secara dinamis berdasarkan pada jenis contoh yang dinamis. Saya pikir Pola Pengunjung dapat memberikan apa yang Anda inginkan. Ini memberi Anda apa yang Anda inginkan. Ini sedikit kode tambahan, tetapi bisa bermanfaat bagi pengunjung lain.

Lihat: http://en.wikipedia.org/wiki/Visitor_pattern untuk latar belakang.

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

Kemudian untuk setiap Objek konkret:

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

dan kemudian tentukan pengunjung dasar:

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

Kemudian pengunjung konkret yang memilih fungsi statis yang sesuai:

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

akhirnya, gunakan:

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

Catatan:

  • Constness pergi sebagai latihan.
  • Anda mengembalikan referensi dari statis. Kecuali Anda memiliki singleton, itu dipertanyakan.

Jika Anda ingin menghindari kesalahan salin-tempel di mana salah satu metode kunjungan Anda memanggil fungsi statis yang salah, Anda bisa menggunakan fungsi pembantu templated (yang tidak bisa sendiri virtual) t pengunjung Anda dengan templat seperti ini:

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

metode statis virtual, jika ada, tidak akan bergantung pada apa pun dalam sebuah instance - tetapi instance tersebut perlu mengetahui tipenya untuk memohonnya. Ini dapat dikerjakan oleh kompiler (misalnya dengan menggunakan beberapa struktur data tunggal per kelas dengan pointer ke metode dan anggota virtual statis.) Ini tentu saja bukan kontradiksi dalam hal.
einpoklum

Apakah itu kontradiksi atau tidak adalah masalah semantik. Orang dapat membayangkan C ++ memungkinkan memanggil statika dari sebuah instance (misalnya, Foo foo; ... foo::bar();bukan Foo::bar();). Itu tidak seperti decltype(foo)::bar();tetapi itu lagi akan terikat secara statis. Pendekatan pengunjung tampaknya merupakan cara yang masuk akal untuk mendapatkan perilaku ini tanpa hanya membuat metode statis menjadi metode const virtual.
Ben

0

Dengan c ++ Anda dapat menggunakan pewarisan statis dengan metode crt. Sebagai contoh, ini digunakan secara luas pada templat jendela atl & wtl.

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

Sederhananya, Anda memiliki kelas yang dibuat sendiri seperti kelas myclass: public myancestor. Dari titik ini kelas myancestor sekarang dapat memanggil fungsi T :: YourImpl statis Anda.


-1

Mungkin Anda bisa mencoba solusi saya di bawah ini:

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

Ya, saya tahu, 4 tahun. Menjelaskan -score untuk mereka yang tidak ingin membaca kode dalam banyak detail. Base::mSelfmerujuk pada instance yang paling baru dibangun dari setiap kelas turunan, bahkan jika instance tersebut telah dihancurkan . jadi 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 */yang BUKAN apa yang diinginkan.
Jesse Chisholm

-3

Seperti yang dikatakan orang lain, ada 2 informasi penting:

  1. tidak ada thispointer saat membuat panggilan fungsi statis dan
  2. yang thispointer menunjuk ke struktur di mana tabel virtual, atau dunk, digunakan untuk mencari metode yang runtime untuk memanggil.

Fungsi statis ditentukan pada waktu kompilasi.

Saya menunjukkan contoh kode ini di C + + anggota statis di kelas ; itu menunjukkan bahwa Anda dapat memanggil metode statis yang diberi pointer nol:

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
Secara teknis, ini adalah perilaku yang tidak terdefinisi. Anda tidak dapat menghormati pointer nol karena alasan apa pun. Satu-satunya hal yang dapat Anda lakukan dengan pointer nol adalah a) menetapkan pointer lain untuk itu dan b) membandingkannya dengan pointer lain.
KeithB

1
Selain itu, Anda hanya dapat membandingkannya untuk kesetaraan (atau ketidaksetaraan_ dengan pointer lain, tidak memesan. Yaitu p < null, p >= nulldll semua tidak terdefinisi juga.
Pavel Minaev

1
@KeithB - ​​Untuk kelengkapan Anda juga dapat memanggil delete dengan aman pada null pointer.
Steve Rowe
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.