C ++ statik sanal üyeler?


140

Hem bir üye işlevi olması C ++ mümkün mü staticve virtual? Görünüşe göre, bunu yapmanın kolay bir yolu yok ( static virtual member();derleme hatası), ama aynı etkiyi elde etmenin en azından bir yolu var mı?

IE:

struct Object
{
     struct TypeInformation;

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

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

Karşılaştırmalar için yararlı olabilecek ve şablonlar için hayati önem taşıyan GetTypeInformation()hem example ( object->GetTypeInformation()) hem de class ( SomeObject::GetTypeInformation()) üzerinde kullanmak mantıklıdır .

Düşünebileceğim tek yol, sınıf başına iki işlev / bir işlev ve bir sabit yazmak ya da makro kullanmaktır.

Başka çözüm var mı?


12
Sadece bir yan yorum: statik yöntemler hiçbir durumda yürütülmez, yani bu işaretçiyi örtük olarak göstermezler. Bununla birlikte, constbir yöntem imzasında örtük thisişaretçiyi sabit olarak işaretler ve örtük parametreden yoksun oldukları için statik yöntemlere uygulanamaz.
David Rodríguez - dribeas

2
@cvb: Örneğinizi yansımayı içermeyen kodla değiştirmeyi ciddi bir şekilde tekrar gözden geçiririm. Şu anki şekilde, iki ayrı (ilgili olsa da) sorunu karıştırıyorsunuz. Evet, ve sizden sorduğunuzdan buçuk yıl geçtiğini biliyorum.
einpoklum

Burada zımnen gerekli olan özelliklerden biri, derleyicinin bir hiyerarşideki her nesnenin belirli bir arabirim (yöntemin bir veya daha fazlasının statik olduğu) uyguladığını kontrol etmesidir. Temel olarak, statik yöntem için saf bir sanal kontrol çok mantıklıdır, çünkü statik yöntemi eklemeyi unutursanız, derleyici hata vermelidir . Buradaki anahtar kelime sanal değildir, bu özel durum dışında C ++ ile eşanlamlı olan daha soyuttur . Ne yazık ki, şu anda C ++ ile yapamazsınız.
xryl669

Yanıtlar:


75

Hayır, bunu yapmanın bir yolu yok, çünkü aradığınızda ne olur Object::GetTypeInformation()? İlişkili bir nesne olmadığı için hangi türetilmiş sınıf sürümünü çağıracağını bilemez.

Düzgün çalışması için statik olmayan bir sanal işlev yapmanız gerekir; bir nesne örneği olmadan sanal olarak türetilmiş belirli bir sınıfın sürümünü de çağırabiliyorsanız, ikinci bir yeniden yükseltici statik sanal olmayan sürümü de sağlamanız gerekir.


8
Statik sınıfı (veya statik üyeleri sınıflar) tek birton olarak görürseniz, her şey açık hale gelir - sizin durumunuzda, temel sınıf örneğinde normal sanal yöntemi çağırmakla aynı şekilde Object :: GetTypeInformation çağrılmalıdır . (Tabii ki, eğer C ++ sanal statik yöntemleri destekli)
Spook

13
Bu tamamen fena bir argüman. Sınıfı nesne yerine kullanırsanız, sanal dağıtım yapmak yerine doğal olarak bu sınıftan sürümü kullanır. Orada yeni bir şey yok.
Deduplicator

54

Birçoğu bunun mümkün olmadığını, bir adım daha ileri gideceğini ve bunun anlamlı olmadığını söylüyor.

Statik üye, herhangi bir örnekle, yalnızca sınıfla ilgili olmayan bir şeydir.

Sanal üye herhangi bir sınıfla doğrudan ilişkili olmayan, sadece bir örnekle ilgili olan bir şeydir.

Dolayısıyla, statik bir sanal üye herhangi bir örnek veya sınıfla ilgili olmayan bir şey olacaktır.


42
Sınıfların birinci sınıf değerler olduğu dillerde mükemmel anlamlıdır - örneğin Delphi'de buna sahiptir ve ayrıca "statik sanal" yöntemlere sahiptir.
Pavel Minaev

4
Kesinlikle. "Sanal işlev" (tanım gereği) dinamik olarak bağlı bir işlevdir , yani belirli bir nesnenin dinamik türüne bağlı olarak çalışma zamanında seçilir . Bu nedenle, nesne yok = sanal çağrı yok.
Kos

7
Statik sanalların da anlamlı olduğunu düşünüyorum. Arabirim sınıflarını tanımlamak ve türetilmiş sınıfta uygulanması gereken statik yöntemleri dahil etmek mümkün olacaktır.
bkausbk

34
Bir static virtualyöntem için çok anlamlı değil , ancak static saf bir virtual yöntem bir arayüzde çok anlamlı.
Bret Kuhns

4
Bir olması çok anlamlı static const string MyClassSillyAdditionalName.
einpoklum

23

Geçen gün bu soruna rastladım: Statik yöntemlerle dolu bazı sınıflarım vardı ama miras ve sanal yöntemleri kullanmak ve kod tekrarını azaltmak istedim. Benim çözümüm şuydu:

Statik yöntemler kullanmak yerine, sanal yöntemlerle bir singleton kullanın.

Başka bir deyişle, her sınıf, sınıfın tek bir paylaşılan örneğine bir işaretçi almak için çağırdığınız statik bir yöntem içermelidir. Gerçek yapıcıları özel veya korumalı hale getirebilirsiniz, böylece dış kod ek örnekler oluşturarak kötüye kullanamaz.

Pratikte, tek birton kullanmak, miras ve sanal yöntemlerden yararlanabilmeniz dışında statik yöntemler kullanmaya çok benzer.


Bu bana performansa mal olacak - derleyici aşağıdakilerden emin olamazsa: 1. Aslında bir singleton ve 2. Hiçbir şey miras kalmaz, tüm ek yükü optimize edebileceğini sanmıyorum.
einpoklum

Bu tür şeylerin performansı sizi endişelendiriyorsa, C # muhtemelen sizin için yanlış dildir.
Nate CK

3
Ah, iyi bir noktaya değindin. Açıkçası, 2009'da yazdığımdan beri bunu düşündüğümden beri bir süredir. Bunu başka bir şekilde ifade edeyim, o zaman: bu tür bir performans şey sizi endişelendiriyorsa, belki de miras kullanımından tamamen kaçınmalısınız. Poster özellikle sanal yöntemler istedi, bu yüzden buraya sanal yöntemlerin yükü hakkında şikayet etmek için gelmeniz garip.
Nate CK

15

Bu mümkün!

Ama tam olarak mümkün olanı daraltalım. İnsanlar aynı işlevi statik çağrı "SomeDerivedClass :: myfunction ()" ve polimorfik çağrı "base_class_pointer-> myfunction ()" yoluyla çağırabilmeleri için gerekli olan kodun kopyalanması nedeniyle genellikle "statik sanal işlev" ister. Bu tür işlevselliğe izin vermek için "yasal" yöntem, işlev tanımlarının çoğaltılmasıdır:

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

Temel sınıfın çok sayıda statik işlevi varsa ve türetilmiş sınıfın her birini geçersiz kılması gerekiyorsa ve biri sanal işlev için yinelenen bir tanım vermeyi unutmuşsa. Doğru, çalışma zamanı boyunca izlemesi zor olan garip bir hata alırız . Kod çoğaltılması kötü bir şeydir. Aşağıdaki bu sorunu çözmeye çalışır (ve önceden tamamen güvenli olduğunu ve typeid veya dynamic_cast's gibi herhangi bir kara büyü içermediğini söylemek istiyorum :)

Yani, türetilmiş sınıf başına sadece bir getTypeInformation () tanımı sunmak istiyoruz ve bunun statik bir tanım olması gerektiği açıktır.getTypeInformation () sanalsa "SomeDerivedClass :: getTypeInformation ()" çağrılması mümkün olmadığından, İşaretçi sınıftan taban sınıfa kadar türetilmiş sınıfın statik fonksiyonunu nasıl çağırabiliriz? Vtable ile bu mümkün değildir, çünkü vtable yalnızca sanal işlevlere işaretçiler depolar ve sanal işlevleri kullanmamaya karar verdiğimizden, vtable'ı bizim yararımız için değiştiremeyiz. Daha sonra, türetilmiş sınıfın statik işlevine işaretçi ile taban sınıfa erişebilmek için, bir şekilde bir nesnenin türünü temel sınıfında depolamamız gerekir. Bir yaklaşım, temel sınıfı "merakla yinelenen şablon kalıbı" kullanılarak geçici hale getirmektir, ancak burada uygun değildir ve "tip silme" adı verilen bir teknik kullanacağız:

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

Şimdi bir nesne türünü temel nesne "Object" içinde bir değişken "keeper" ile depolayabiliriz:

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

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

};

Türetilmiş bir sınıfta kaleci inşaat sırasında başlatılmalıdır:

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

Sözdizimsel şeker ekleyelim:

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

Şimdi torunların beyanları şöyle:

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

kullanımı:

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

Avantajları:

  1. daha az kod çoğaltılması (ancak her kurucuda OVERRIDE_STATIC_FUNCTIONS öğesini çağırmamız gerekir)

Dezavantajları:

  1. Her kurucuda OVERRIDE_STATIC_FUNCTIONS
  2. bellek ve performans yükü
  3. artan karmaşıklık

Açık sorunlar:

1) Statik ve sanal fonksiyonlar için belirsizlikler nasıl çözülür?

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) her kurucu içinde dolaylı olarak OVERRIDE_STATIC_FUNCTIONS nasıl çağırılır?


Çaba için +1, ancak bunun sadece sanal yöntemlerle işlevselliği bir singletona devretmekten daha zarif olduğundan emin değilim.
einpoklum

1
@einpoklum, bunun tercih edilebileceği bir durum düşünebilirim. Zaten statik yöntemleri çağıran çok sayıda istemci kodumuz olduğunu varsayalım. Statik yöntemlerden sanal yöntemlerle singleton'a geçmek, yukarıda sunulan çözüm invaziv olmadığında istemci kodunda değişiklik yapılmasını gerektirir.
Alsk

"Foo :: getTypeInformation" ve "TypeKeeperImpl :: getTypeInformation" için "sanal" anahtar kelime gerekli değildir.
bartolo-otrit

12

Alsk zaten oldukça ayrıntılı bir cevap vermiş olsa da, onun alternatif uygulamasının aşırı karmaşık olduğunu düşündüğüm için bir alternatif eklemek istiyorum.

Tüm nesne türleri için arabirim sağlayan soyut bir temel sınıfla başlıyoruz:

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

Şimdi gerçek bir uygulamaya ihtiyacımız var. Ancak hem statik hem de sanal yöntemleri yazmak zorunda kalmamak için, gerçek nesne sınıflarımız sanal yöntemleri miras alacaktır. Bu sadece temel sınıf statik üye işlevine nasıl erişileceğini bilirse işe yarar. Bu yüzden bir şablon kullanmalıyız ve asıl nesneler sınıf adını ona geçirmeliyiz:

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

Son olarak gerçek nesnelerimizi uygulamalıyız. Burada yalnızca statik üye işlevini uygulamamız gerekiyor, sanal üye işlevleri, türetilmiş sınıfın adıyla başlatılan ObjectImpl şablon sınıfından miras alınacak, böylece statik üyelerine erişecek.

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

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

Test etmek için bir kod ekleyelim:

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

Zeyilname (12 Ocak 2019):

GetClassNameStatic () işlevini kullanmak yerine, sınıf adını statik bir üye olarak tanımlayabilirsiniz, hatta IIRC'nin C ++ 11'den beri çalıştığı "satır içi" bile (tüm değiştiricilerden korkmayın :)):

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

Bu mümkün. İki işlev yapın: statik ve sanal

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
Ayrıca, statik yöntemler sabit olamaz. Sadece mantıklı değil, hangi örneği mutasyona uğratmayacaklar?
David Rodríguez - dribeas

1
Bu çoğunlukla sadece kod çoğaltmadır. Buradaki fikir, alt sınıfların yalnızca statik sabit üyeye sahip olmaları, ona erişebilecek kodlara sahip olmaları gerekmemesidir.
einpoklum

8

Hayır, bu mümkün değildir, çünkü statik üye işlevlerinde bir thisişaretçi yoktur. Ve statik üyeler (hem fonksiyonlar hem de değişkenler) aslında sınıf üyesi değildirler. Sadece çağrılırlar ClassName::memberve sınıf erişim belirleyicilerine uyarlar. Depolanmaları sınıfın dışında bir yerde tanımlanır; sınıfın bir nesnesini her başlattığınızda depolama oluşturulmaz. Sınıf üyelerine işaretçiler semantik ve sözdiziminde özeldir. Statik bir üyeye işaretçi, her bakımdan normal bir işaretçidir.

bir sınıftaki sanal işlevler thisişaretçiye ihtiyaç duyar ve sınıfa çok bağlıdır, dolayısıyla statik olamazlar.


1
Yalnızca statik olmayan işlevlerin bir this işaretçiye ihtiyacı vardır . statik işlevler bir örneğe özgü değildir ve buna gerek yoktur. Yani - bu sanal statik üyelerin imkansız olmasının bir nedeni değil.
einpoklum

7

Peki, oldukça geç bir cevap ama merakla tekrar eden şablon desenini kullanmak mümkün. Bu wikipedia makalesi ihtiyacınız olan bilgiye sahiptir ve ayrıca statik polimorfizm altındaki örnek istenir.


3

Bence yapmaya çalıştığınız şey şablonlar aracılığıyla yapılabilir. Buradaki satırlar arasında okumaya çalışıyorum. Yapmaya çalıştığınız, bazı kodlardan türetilmiş bir sürümü çağırdığı ancak arayan kişinin hangi sınıfı belirtmediği bir yöntemi çağırmaktır. Misal:

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

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

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

int main()
{
    Try();
}

Try () işlevinin, Bar belirtmeden M'nin Bar sürümünü çağırmasını istiyorsunuz. Statik için bunu yapmanın yolu bir şablon kullanmaktır. Yani şu şekilde değiştirin:

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

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

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

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

1
Kodunuzu 4 boşluk girintilerseniz, otomatik olarak biçimlendirebilirsiniz. Alternatif olarak, aynı amacı satır içi elde etmek için arka kene kullanabileceğinize inanıyorum.
chollida

1
Bu kaçırdığım aşikar. Teşekkür ederim. Yine de, kasık üyeler tuhaf.
allesblinkt

M () statik işlev değildir. T :: M () nasıl denir?
DDukDDak99

3

Hayır, statik üye işlevi sanal olamaz. Sanal kavram vptr yardımıyla çalışma zamanında çözüldüğünden ve vptr bir sınıfın statik olmayan bir üyesidir. Bu statik üye işlevi nedeniyle vptr'e erişemez, böylece statik üye sanal olmayın.


2
Yalnızca örneğe özgü sanal yöntemler, örneklerin seçilebilir olmasını gerektirir. Statik - sınıf başına bir - vtable olabilir. Örneklerin bilmesini istiyorsanız, örneğin vtable öğesinden vtable sınıf statikine işaret edin.
einpoklum

2

Bu mümkün değil, ama bu sadece bir ihmal yüzünden. Birçok insanın iddia ettiği gibi "anlamsız" bir şey değil. Açık olmak gerekirse, bunun gibi bir şeyden bahsediyorum:

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

Bu% 100 şeydir olabilir (sadece değil vardır) uygulanacak ve ben yararlı bir şey iddia ediyorum.

Normal sanal işlevlerin nasıl çalıştığını düşünün. S'yi kaldırın staticve başka şeyler ekleyin ve elimizde:

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

Bu iyi çalışır ve temelde ne olur derleyici VTables adlı iki tablo yapar ve böyle sanal işlevlere endeksler atar

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

Daha sonra sanal işlevlere sahip her sınıf, VTable'ı işaret eden başka bir alanla artırılır, böylece derleyici temel olarak bunları şu şekilde değiştirir:

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

O zaman aradığınızda gerçekte ne olur b->sayMyName()? Temel olarak bu:

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

(İlk parametre olur this.)

Tamam, peki statik sanal işlevlerle nasıl çalışır? Peki, statik ve statik olmayan üye fonksiyonları arasındaki fark nedir? Tek fark, ikincisinin bir thisişaretçi almasıdır.

Aynı şeyi statik sanal işlevlerle de yapabiliriz - sadece thisişaretçiyi kaldırın .

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

Bu daha sonra her iki sözdizimini de destekleyebilir:

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

Bu yüzden tüm muhalifleri görmezden gelin. Bu does mantıklı. O zaman neden desteklenmiyor? Bunun çok az yararı olduğu ve hatta biraz kafa karıştırıcı olabileceği için düşünüyorum.

Normal bir sanal işleve göre tek teknik avantaj, işleve geçmeniz gerekmemesidir, thisancak bunun performansta ölçülebilir bir fark yaratacağını düşünmüyorum.

Bir örneğiniz olduğunda ve bir örneğiniz olmadığında durumlar için ayrı bir statik ve statik olmayan fonksiyonunuz olmadığı anlamına gelir, ancak aynı zamanda kullandığınızda bunun gerçekten "sanal" olması kafa karıştırıcı olabilir örnek çağrısı.


0

Hayır, bu mümkün değildir, çünkü statik üyeler derleme zamanında bağlanırken, sanal üyeler çalışma zamanında bağlanır.


0

İlk olarak, yanıtlar OP'nin talep ettiği şeyin terimler açısından bir çelişki olduğu doğrudur: sanal yöntemler bir örneğin çalışma zamanı türüne bağlıdır; statik işlevler özellikle bir örneğe bağlı değildir - yalnızca bir türe bağlıdır. Bununla birlikte, statik fonksiyonların bir türe özgü bir şey döndürmesi mantıklıdır. Örneğin, Devlet kalıbı için MouseTool sınıflarının bir ailesi vardı ve her birinin kendisiyle birlikte gelen klavye değiştiricisini döndüren statik bir işlevi olmasını sağladım; MouseTool örneğini doğru yapan fabrika işlevinde bu statik işlevleri kullandım. Bu işlev, fare durumunu MouseToolA :: keyboardModifier (), MouseToolB :: keyboardModifier () vb. Yönünden kontrol etti ve sonra uygun olanı başlattı. Tabii ki daha sonra devletin haklı olup olmadığını kontrol etmek istedim, bu yüzden "

Bu yüzden, kendinizi bunu isterken bulursanız, çözümünüzü düzeltmek isteyebilirsiniz. Yine de, statik yöntemlere sahip olma arzusunu anlıyorum ve daha sonra bunları dinamik bir örnek türüne göre dinamik olarak çağırıyorum. Ziyaretçi Paterninin size istediğinizi verebileceğini düşünüyorum . Size ne istediğinizi verir. Biraz ekstra kod, ancak diğer ziyaretçiler için yararlı olabilir.

Arka plan için bkz . 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;
};

Sonra her somut nesne için:

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

ve ardından temel ziyaretçiyi tanımlayın:

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

Ardından uygun statik fonksiyonu seçen somut ziyaretçi:

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

Son olarak, kullanın:

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

Notlar:

  • Constness bir egzersiz olarak kaldı.
  • Statik bir referans döndürdünüz. Bir singletonuz yoksa, bu şüphelidir.

Ziyaret yöntemlerinizden birinin yanlış statik işlevi çağırdığı kopyala yapıştırma hatalarından kaçınmak istiyorsanız, ziyaretçinize aşağıdaki gibi bir şablonla şablonlanmış bir yardımcı işlev kullanabilirsiniz (kendisi sanal olamaz):

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

Var olsaydı sanal statik yöntemler bir örnekte hiçbir şeye bağlı olmazdı - ancak örneğin onları çağırmak için türünü bilmesi gerekir. Bu, bir derleyici tarafından çözülebilir (örneğin, sanal statik yöntemlere ve üyelere işaret eden sınıf başına bazı tek veri yapıları kullanılarak.) Kesinlikle bir çelişki değildir.
einpoklum

Bunun bir çelişki olup olmadığı anlambilim meselesidir. Bir örnek C ++ bir örnekten (örneğin, Foo foo; ... foo::bar();yerine Foo::bar();) statik çağırmaya izin hayal olabilir . Bu aksine decltype(foo)::bar();değil, yine statik olarak bağlı olacaktır. Ziyaretçi yaklaşımı, statik yöntemi sanal bir sabit yöntem haline getirmeden bu davranışı elde etmenin makul bir yolu gibi görünmektedir.
Ben

0

C ++ ile crt yöntemiyle statik miras kullanabilirsiniz. Örneğin, atl & wtl pencere şablonunda yaygın olarak kullanılmaktadır.

Bkz. Https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Basit olmak gerekirse, sınıf myclass gibi kamusal bir sınıfınız var: public myancestor. Bu noktadan sonra myancestor sınıfı statik T :: YourImpl işlevinizi çağırabilir.


-1

Belki benim çözümümü aşağıda deneyebilirsiniz:

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

Evet, biliyorum, 4 yıl. Kodu bu kadar ayrıntılı okumak istemeyenler için -score'u açıklamak. Base::mSelf"Hile" terimi, herhangi bir türetilmiş sınıfın en son oluşturulmuş örneğidir, bu örnek yok edilmiş olsa bile . yani 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 */İstenen ne DEĞİLDİR.
Jesse Chisholm

-3

Diğerlerinin söylediği gibi, 2 önemli bilgi vardır:

  1. thisstatik işlev çağrısı yaparken işaretçi yoktur ve
  2. thisSanal tablo veya thunk, çağrı için hangi çalışma yöntem aramak için kullanılan yapıya işaretçi işaret eder.

Derleme zamanında statik bir işlev belirlenir.

Bu kod örneğini sınıfta C ++ statik üyelerinde gösterdi ; bir boş gösterici verilen statik bir yöntemi çağırabileceğinizi gösterir:

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
Teknik olarak, bu tanımsız bir davranıştır. Herhangi bir nedenden dolayı boş göstericiyi erteleyemezsiniz. Boş bir işaretçi ile yapabileceğiniz tek şey a) üzerine başka bir işaretçi atamak ve b) başka bir işaretçi ile karşılaştırmaktır.
KeithB

1
Dahası, bunu sadece eşitlik (veya eşitsizlik_ ile başka bir işaretçi ile karşılaştırabilir, sipariş veremezsiniz. Yani p < null, p >= nullvb.) De tanımlanmamıştır.
Pavel Minaev

1
@KeithB - ​​Bütünlük için güvenli bir boş göstericide silme çağrısı da yapabilirsiniz.
Steve Rowe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.