Neden C ++ 'da sanal fonksiyonlara ihtiyacımız var?


1312

C ++ öğreniyorum ve sadece sanal işlevlere giriyorum.

Okuduğumdan (kitapta ve çevrimiçi olarak), sanal işlevler temel sınıfta türetilmiş sınıflarda geçersiz kılabileceğiniz işlevlerdir.

Ancak kitapta daha önce, temel kalıtım hakkında bilgi edinirken, türetilmiş sınıflardaki temel işlevleri kullanmadan geçersiz kılabildim virtual.

Peki burada ne eksik? Sanal işlevler hakkında daha fazla şey olduğunu biliyorum ve önemli gibi görünüyor, bu yüzden tam olarak ne olduğu konusunda net olmak istiyorum. Çevrimiçi olarak doğrudan bir cevap bulamıyorum.


13
Sanal işlevler için burada pratik bir açıklama hazırladım: nrecursions.blogspot.in/2015/06/…
Nav

4
Bu belki de sanal işlevlerin en büyük yararıdır - kodunuzu yeni türetilmiş sınıfların eski kodla değiştirilmeden otomatik olarak çalışacak şekilde yapılandırma yeteneği!
user3530616

tbh, sanal işlevler tip silme için OOP'un temel özelliğidir. Bence sanal olmayan yöntemler, Object Pascal ve C ++ 'ı özel kılan, gereksiz büyük vtable'ın optimizasyonu ve POD uyumlu sınıflara izin verme. Birçok OOP dili her yöntemin geçersiz kılınmasını bekler .
Swift - Cuma Pastası

Bu iyi bir soru. Aslında C ++ 'daki bu sanal şey, Java veya PHP gibi diğer dillerde soyutlanır. C ++ 'da bazı nadir durumlar için biraz daha fazla kontrol elde edersiniz (Çoklu kalıtımdan veya DDOD'un bu özel durumundan haberdar olun ). Peki bu soru neden stackoverflow.com'da yayınlanıyor?
Edgar Alloro

Erken bağlama-geç bağlama ve VTABLE'a bakarsanız daha makul ve mantıklı olacağını düşünüyorum. Burada iyi bir açıklama var ( learncpp.com/cpp-tutorial/125-the-virtual-table ).
ceyun

Yanıtlar:


2729

İşte sadece neyi değil virtual işlevlerin , neden gerekli olduklarını :

Diyelim ki şu iki sınıfa sahipsiniz:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

Ana işlevinizde:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

Şimdiye kadar çok iyi, değil mi? Hayvanlar jenerik yiyecekler, kediler sıçanlar, hepsi olmadan yiyorlar virtual.

Şimdi biraz değiştirelim, böylece eat()bir ara işlev (sadece bu örnek için önemsiz bir işlev) ile çağrılır:

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

Şimdi ana fonksiyonumuz:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

Uh oh ... bir kediyi geçtik func(), ama sıçanları yemez. Eğer aşırı Should func()bir sürer böylece Cat*? Hayvandan daha fazla hayvan türetmek zorunda kalırsanız, hepsinin kendi hayvanlarına ihtiyacı olacaktır func().

Solüsyon yapmaktır eat()gelen Animalsınıfın sanal işlevi:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

Ana:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

Bitti.


165
Bunu doğru anlıyorsam, nesne, üst sınıf olarak kabul ediliyor olsa bile, sanal alt sınıf yönteminin çağrılmasına izin verir mi?
Kenny Worden

147
Geç bağlanma aracı işlevi "func" örneği ile açıklamak yerine, burada daha basit bir gösteri - Hayvan * hayvan = yeni Hayvan; // Kedi * kedi = yeni Kedi; Hayvan * kedi = yeni Kedi; Hayvanda> yemek (); // çıktılar: "Genel yemek yiyorum." cat-> yemek (); // çıktılar: "Genel yemek yiyorum." Alt sınıf nesnesini (Cat) atamış olsanız bile, çağrılan yöntem işaret ettiği nesnenin türüne değil, işaretçi türüne (Hayvan) dayanır. Bu yüzden "sanal" lazım.
rexbelia

37
C ++ 'da bu varsayılan davranışı bulan tek kişi ben miyim? "Sanal" olmadan kod çalışması beklenebilirdi.
David 天宇 Wong

20
@David 天宇 Wong Sanırım virtualstatik ile statik arasındaki bazı dinamik bağları tanıtıyoruz ve evet Java gibi dillerden geliyorsanız garip.
peterchaula

32
Her şeyden önce, sanal çağrılar normal işlev çağrılarından çok, çok daha pahalıdır. C ++ felsefesi varsayılan olarak hızlıdır, bu nedenle varsayılan olarak sanal çağrılar büyük bir hayır. İkinci neden, bir kütüphaneden bir sınıfı devralırsanız ve temel sınıf davranışını değiştirmeden genel veya özel bir yöntemin (dahili olarak sanal bir yöntemi çağıran) dahili uygulamasını değiştirirse sanal çağrıların kodunuzun kırılmasına yol açmasıdır.
saolof

672

"Sanal" olmadan "erken bağlanma" elde edersiniz. Yöntemin hangi uygulamasının kullanıldığını derleme zamanında, aradığınız işaretçinin türüne göre karar verilir.

"Sanal" ile "geç bağlama" elde. Yöntemin hangi uygulamasının kullanıldığı, sivri uçlu nesnenin türüne (başlangıçta ne gibi inşa edildiğine) bağlı olarak çalışma zamanında kararlaştırılır. Bu, nesneyi gösteren işaretçinin türüne bağlı olarak düşündüğünüz şey değildir.

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

DÜZENLE - bu soruya bakın .

Ayrıca - bu eğitim C ++ erken ve geç bağlama kapsar.


11
Mükemmel ve hızlı bir şekilde ve daha iyi örneklerin kullanımı ile eve döner. Ancak bu basittir ve soru soran kişi parashift.com/c++-faq-lite/virtual-functions.html sayfasını gerçekten okumalıdır . Diğer millet zaten bu konuya bağlı SO makalelerde bu kaynağa işaret var, ama bu yeniden söz değer olduğuna inanıyorum.
Sonny

36
Erken ve geç bağlama özellikle c ++ topluluğu içinde kullanılan terimler olup olmadığını bilmiyorum , ancak doğru terimler statik (derleme zamanında) ve dinamik (çalışma zamanında) bağlama.
mike

31
@mike - "" Geç bağlama "terimi, en azından 1960'lara dayanıyor ve ACM'nin İletişiminde bulunabilir." . Her konsept için tek bir doğru kelime olması iyi olmaz mıydı? Ne yazık ki, öyle değil. "Erken bağlama" ve "geç bağlama" terimleri C ++ ve hatta nesne yönelimli programlamadan önce gelir ve kullandığınız terimler kadar doğrudur.
Steve314

4
@BJovke - bu cevap C ++ 11 yayınlanmadan önce yazılmıştır. Buna rağmen, sadece herhangi bir sorun (varsayılan olarak 14 C ++ kullanarak) GCC 6.3.0 bunu derlenmiş - Açıkçası değişken beyanı ve aramaları sarma mainvb fonksiyonu Pointer-to-türevli örtülü atmalarını işaretçi ile taban (daha özel olarak dolaylı olarak daha genel bir şekilde yayınlanır). Bunun tam tersi, genellikle a dynamic_cast. Başka bir şey - tanımlanmamış davranışa çok eğilimli, bu yüzden ne yaptığınızı bildiğinizden emin olun. Bildiğim kadarıyla, bu daha önce bile C ++ 98 değişmedi.
Steve314

10
Günümüzde C ++ derleyicilerinin genellikle bağlamanın ne olacağından emin olabildikleri zaman geç bağlamaya erken geçiş yapabileceğini unutmayın. Buna "sanallaştırma önleme" de denir.
einpoklum

83

Bunu göstermek için en az 1 miras ve mahzun gerekir. İşte çok basit bir örnek:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    std::cout << d->Says();   // always Woof
    std::cout << a->Says();   // Woof or ?, depends on virtual
}

39
Örneğiniz, döndürülen dizenin işlevin sanal olup olmadığına bağlı olduğunu, ancak hangi sonucun sanal olana ve hangisinin sanal olmayana karşılık gelmediğini söylemez. Ayrıca, döndürülen dizeyi kullanmadığınız için biraz kafa karıştırıcı.
Ross

7
Sanal anahtar kelime ile: Woof . Sanal anahtar kelime olmadan: ? .
Hesham Eraqi

@HeshamEraqi sanal olmadan erken bağlama ve "?" Gösterecektir sınıf dersi
Ahmad

46

Güvenli iniş , basitlik ve kısalık için sanal yöntemlere ihtiyacınız var .

Sanal yöntemler bunu yapar: aksi takdirde sahip olacağınız daha karmaşık ve ayrıntılı koddaki güvenli olmayan manuel dökümlerden kaçınarak, görünüşte basit ve özlü kodla güvenli bir şekilde indirilirler.


Sanal olmayan yöntem ⇒ statik bağlama

Aşağıdaki kod bilerek “yanlış” tır. valueYöntemi olarak bildirmez virtualve bu nedenle istenmeyen bir “yanlış” sonuç verir, yani 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

"Kötü" olarak yorumlanan satırda Expression::value, statik olarak bilinen tür (derleme zamanında bilinen tür ) olduğu için yöntem çağrılır Expressionve valueyöntem sanal değildir.


Sanal yöntem ⇒ dinamik bağlama.

Bildirmek valueolarak virtualstatik bilinen tip içinde Expressionher bir arama objenin gerçek türü bu ne olduğunu kontrol ve ilgili uygulanmasını arayacak olmasını sağlar valuebunun için dinamik türü :

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

6.86Sanal yöntem neredeyse çağrıldığı için burada çıktı olması gerektiği gibi . Buna çağrıların dinamik bağlanması da denir . Nesnenin gerçek dinamik türünü bulan küçük bir kontrol yapılır ve bu dinamik tür için ilgili yöntem uygulaması çağrılır.

İlgili uygulama en spesifik (en türetilmiş) sınıftır.

Buradaki türetilmiş sınıflardaki yöntem uygulamalarının işaretlenmediğini virtual, bunun yerine işaretlendiğini unutmayın override. İşaretlenebilirler, virtualancak otomatik olarak sanaldırlar. overrideVarsa o anahtar kelime olmasını sağlar değil bazı temel sınıf böyle bir sanal yöntem, o zaman (arzu edilmektedir) bir hata alırsınız.


Bunu sanal yöntemler olmadan yapmanın çirkinliği

virtualBiri olmadan dinamik bağlama bazı Do It Yourself sürümünü uygulamak zorunda kalacaktı . Genellikle güvensiz manuel iniş, karmaşıklık ve ayrıntı içerir.

Tek bir işlev söz konusu olduğunda, burada olduğu gibi, bir işlev işaretçisini nesneye depolamak ve bu işlev işaretçisi aracılığıyla çağırmak yeterlidir, ancak buna rağmen, güvenli olmayan bazı downcast'leri, karmaşıklığı ve ayrıntı düzeyini içerir:

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Buna bakmanın olumlu bir yolu, yukarıdaki gibi güvensiz iniş, karmaşıklık ve ayrıntılarla karşılaşırsanız, genellikle sanal bir yöntem veya yöntemler gerçekten yardımcı olabilir.


40

Sanal İşlevler, Çalışma Zamanı Polimorfizmini desteklemek için kullanılır .

Yani, sanal anahtar kelime derleyiciye (işlev bağlama) kararını derleme zamanında yapmamasını, çalışma zamanı için ertelemesini söyler " .

  • virtualTemel sınıf bildiriminde anahtar kelimenin önüne geçerek bir işlevi sanal yapabilirsiniz . Örneğin,

     class Base
     {
        virtual void func();
     }
  • Bir zaman Temel Sınıf sanal üye işlevi vardır, herhangi bir sınıf temel sınıf devralır ki yeniden tanımlamak ile fonksiyon tam olarak aynı prototip yani sadece işlevselliği, yeniden fonksiyonun değil arayüz edilebilir.

     class Derive : public Base
     {
        void func();
     }
  • Bir Base sınıfı işaretçisi, Base sınıf nesnesinin yanı sıra Derived sınıf nesnesini işaret etmek için kullanılabilir.

  • Bir temel sınıf işaretçisi kullanılarak sanal işlev çağrıldığında, derleyici çalışma zamanında işlevin hangi sürümünün (örneğin, Temel sınıf sürümü veya geçersiz kılınan Türetilmiş sınıf sürümü) çağrılacağına karar verir. Buna Çalışma Zamanı Polimorfizmi denir .

34

Temel sınıf Baseve türetilmiş bir sınıf ise Der, Base *paslında bir örneğine işaret eden bir işaretçiniz olabilir Der. Aradığınızda p->foo();ise, fooolduğu değil sanal ardından Basebunun 'ın versiyonu gerçeğini göz ardı ederek yürütür paslında bir puan Der. Foo Eğer bir sanal, p->foo()bir "leafmost" geçersiz kılma yürütür footam olarak dikkate sivri-öğenin gerçek sınıf alarak. Sanal ve sanal olmayan arasındaki fark aslında çok önemlidir: birincisi , OO programlamanın temel konsepti olan çalışma zamanı polimorfizmine izin verirken, ikincisi bunu yapmaz.


8
Seninle çelişmekten nefret ediyorum, ama derleme zamanı polimorfizmi hala polimorfizm. Üye olmayan işlevleri aşırı yüklemek bile, bağlantınızdaki terminolojiyi kullanan bir tür polimorfizm - ad-hoc polimorfizmdir. Buradaki fark erken ve geç bağlanma arasındadır.
Steve314

7
@ Steve314, bilgiç olarak haklısın (diğer bir bilgiç olarak, onaylıyorum ;-) - eksik sıfat eklemek için cevabı düzenliyor ;-).
Alex Martelli

26

Sanal İşlev İhtiyacı açıklandı [Anlaşılması kolay]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

Çıktı:

Hello from Class A.

Ancak sanal işlevle:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

Çıktı:

Hello from Class B.

Böylece sanal işlev ile çalışma zamanı polimorfizmi elde edebilirsiniz.


25

Yukarıda belirtilen cevaplarla aynı kavramı kullanıyor olsa da Sanal fonksiyonun başka bir kullanımını eklemek istiyorum, ancak bahsetmeye değer olduğunu düşünüyorum.

SANAL DESTRUCTOR

Base sınıfı yıkıcıyı sanal olarak bildirmeden aşağıdaki programı düşünün; Cat belleği temizlenmeyebilir.

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Çıktı:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Çıktı:

Deleting an Animal name Cat
Deleting an Animal

11
without declaring Base class destructor as virtual; memory for Cat may not be cleaned up.Bundan daha kötü. Temel bir işaretçi / referans aracılığıyla türetilmiş bir nesneyi silmek, tanımsız bir davranıştır. Yani, sadece bir miktar bellek sızabilir. Derleyici şey haline dönüştürmek böylece Aksine, program kötü oluşur: Makine koduna olur iş para cezasına veya burnundan hiçbir şey ya da celp şeytanlar yapar, ya vs Yani bir program böyle tasarlanmıştır eğer, neden en bazı kullanıcı bir yolu olabilir bir taban referans yoluyla türetilmiş bir örneğini silmek, baz gerekir sanal yıkıcı var
underscore_d

21

Geçersiz kılma ve aşırı yükleme arasında ayrım yapmanız gerekir. virtualAnahtar kelime olmadan yalnızca temel sınıf yöntemini aşırı yüklersiniz. Bu saklanmaktan başka bir şey ifade etmiyor. Diyelim ki her ikisinin de uyguladığı bir temel sınıfınız Baseve türetilmiş bir sınıfınız Specializedvar void foo(). Şimdi Basebir örneğine işaret etmek için bir işaretçiniz var Specialized. Onu aradığınızda foo(), farkı yaratabilirsiniz virtual: Yöntem sanal ise, uygulaması Specializedkullanılacaktır, eksikse, sürümü Baseseçilecektir. Temel sınıftan yöntemleri asla aşırı yüklememek en iyi uygulamadır. Sanal olmayan bir yöntem yapmak, yazarının, alt sınıflardaki uzantısının amaçlanmadığını söylemenin yoludur.


3
virtualSen olmadan aşırı yüklenmiyorsun. Sen edilir gölgeleme . Bir temel sınıf Bbir veya daha fazla işleve sahipse foove türetilmiş sınıf Dbir fooad tanımlarsa , bu -s foo öğelerinin tümünü gizler . Bunlara kapsam çözünürlüğü kullanılarak ulaşılır . Aşırı yükleme için fonksiyonları teşvik etmek için kullanmanız gerekir . fooBB::fooB::fooDusing B::foo
Kaz

20

Neden C ++ 'da Sanal Yöntemlere ihtiyacımız var?

Hızlı cevap:

  1. Bize nesne yönelimli programlama için gerekli "içeriklerden" 1 birini sağlar .

Bjarne Stroustrup C ++ Programlama: İlkeler ve Uygulama, (14.3):

Sanal işlev, bir temel sınıftaki bir işlevi tanımlama ve bir kullanıcı temel sınıf işlevini çağırdığında çağrılan türetilmiş bir sınıfta aynı ada sahip bir işleve sahip olma yeteneği sağlar. Buna genellikle çalışma zamanı polimorfizmi , dinamik gönderme veya çalışma zamanı gönderme denir, çünkü çağrılan işlev kullanılan nesnenin türüne göre çalışma zamanında belirlenir.

  1. Bir sanal fonksiyon çağrısına ihtiyacınız varsa en hızlı ve verimli uygulama 2 .

Bir sanal aramayı yönetmek için, türetilmiş nesne 3 ile ilgili bir veya daha fazla veri parçasına ihtiyaç duyulur . Genellikle yapılan yol, fonksiyonlar tablosunun adresini eklemektir. Bu tablo genellikle sanal tablo veya sanal işlev tablosu olarak adlandırılır ve adresi genellikle sanal işaretçi olarak adlandırılır . Her sanal işlev sanal tabloda bir yuva alır. Arayanın nesne (türetilmiş) türüne bağlı olarak, sanal işlev, sırayla, ilgili geçersiz kılmayı başlatır.


1. Kalıtım, çalışma zamanı polimorfizmi ve kapsülleme kullanımı nesne yönelimli programlamanın en yaygın tanımıdır .

2. Çalışma zamanında alternatifler arasından seçim yapmak için işlevselliği daha hızlı veya daha az bellek kullanmak için kodlayamazsınız. Bjarne Stroustrup C ++ Programlama: İlkeler ve Uygulama. (14.3.1) .

3. Sanal işlevi içeren temel sınıfı çağırdığımızda hangi işlevin gerçekten çağrıldığını söyleyecek bir şey.


15

Cevabımı daha iyi okumak için bir konuşma şeklinde yanıtladım:


Neden sanal işlevlere ihtiyacımız var?

Polimorfizm yüzünden.

Polimorfizm nedir?

Temel imlecin türetilmiş yazı nesnelerini de gösterebilmesi.

Polimorfizm'in bu tanımı nasıl sanal fonksiyonlara ihtiyaç duyuyor?

Erken bağlama yoluyla .

Erken bağlama nedir?

C ++ 'da erken bağlama (derleme zamanı bağlama), program yürütülmeden önce bir işlev çağrısının sabitlendiği anlamına gelir.

Yani...?

Bu nedenle, bir işlevin parametresi olarak bir taban türü kullanırsanız, derleyici yalnızca temel arabirimi tanır ve bu işlevi türetilmiş sınıflardan herhangi bir bağımsız değişkenle çağırırsanız, dilimlenir, bu da olmasını istemediğiniz şeydir.

Eğer olmasını istediğimiz şey bu değilse, buna neden izin verilir?

Çünkü Polimorfizme ihtiyacımız var!

O halde Polimorfizmin faydası nedir?

Temel tür işaretçisini tek bir işlevin parametresi olarak kullanabilirsiniz ve daha sonra programınızın çalışma zamanında türetilmiş tür arabirimlerinin her birine (ör. Üye işlevleri) herhangi bir sorun olmadan, bu tekin kayıttan çıkarılmasını kullanarak erişebilirsiniz. temel işaretçi.

Hala hangi sanal fonksiyonların işe yaradığını bilmiyorum ...! Ve bu benim ilk sorumdu!

çünkü sorunuzu çok erken sordunuz!

Neden sanal işlevlere ihtiyacımız var?

Temel işaretçiyle türetilmiş sınıflardan birindeki bir nesnenin adresine sahip bir işlev çağırdığınızı varsayın. Yukarıda bahsettiğimiz gibi, çalışma zamanında, bu işaretçi kayıttan çıkarıldı, şimdiye kadar iyi, ancak, "türetilmiş sınıfımızdan" bir yöntem (== bir üye işlevi) yürütülmesini bekliyoruz! Ancak, aynı sınıfta (aynı başlığa sahip bir yöntem) temel sınıfta zaten tanımlanmış, peki programınız neden diğer yöntemi seçmek için uğraşsın ki? Başka bir deyişle, bu senaryoyu daha önce normalde gördüğümüz şeylerden nasıl anlatabilirsiniz?

Kısa cevap "tabandaki bir sanal üye işlevi" ve biraz daha uzun bir cevap, "bu adımda, program temel sınıfta sanal bir işlev görürse, kullanmaya çalıştığınızı bilir (fark eder) polimorfizm "ve bu nedenle aynı başlığa sahip, ancak - beklenmedik şekilde - farklı bir uygulamaya sahip olan başka bir yöntem bulmak için türetilmiş sınıflara gider ( v-table , geç bağlama biçimi kullanılarak) .

Neden farklı bir uygulama?

Parmak eklemi! Git güzel bir kitap oku !

Tamam, bekle bekle bekle, neden türetilmiş tip işaretçileri kullanabiliyorsa, neden baz işaretçileri kullanmak rahatsız olur? Yargıç sizsiniz, tüm bu baş ağrısına değer mi? Şu iki parçacığa bakın:

// 1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

// 2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

Tamam, ben 1 hala 2 daha iyi olduğunu düşünüyorum rağmen , ya da böyle 1 yazabilirsiniz :

// 1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

ve ayrıca, bunun henüz size açıkladığım tüm şeylerin sadece bir kullanımı olduğunu bilmelisiniz. Bunun yerine, örneğin programınızda, sırasıyla türetilmiş sınıfların her birinden yöntemleri kullanan bir işlevin bulunduğu bir durumu varsayalım (getMonthBenefit ()):

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

Şimdi, bunu baş ağrısı olmadan tekrar yazmaya çalışın !

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

Ve aslında, bu yine de tartışmalı bir örnek olabilir!


2
tek (süper) bir nesne türü kullanarak farklı (alt-) nesneler üzerinde yineleme kavramı vurgulanmalıdır, bu verdiğiniz iyi bir nokta, teşekkürler
harshvchawla

14

Eğer temel sınıf bir işlev varsa, can Redefineveya Overridebunun türetilmiş sınıfta.

Bir yöntemi yeniden tanımlama : Türetilmiş sınıfta temel sınıf yöntemi için yeni bir uygulama verilir. Does not kolaylaştırmakDynamic binding.

Bir yöntemi geçersiz kılmak : Redefiningbirvirtual methodtemel sınıf türetilmiş sınıf. Sanal yöntem Dinamik Bağlamayı kolaylaştırır .

Dediğiniz zaman:

Ancak kitapta daha önce, temel kalıtım hakkında bilgi edinirken, 'sanal' kullanmadan türetilmiş sınıflardaki temel yöntemleri geçersiz kılabildim.

temel sınıftaki yöntem sanal olmadığı için onu geçersiz kılmıyordunuz, aksine yeniden tanımlıyordunuz


11

Altta yatan mekanizmaları biliyorsanız yardımcı olur. C ++, C programcıları tarafından kullanılan bazı kodlama tekniklerini resmileştirir, "katmanlar" kullanılarak değiştirilen "sınıflar" - ortak başlık bölümlerine sahip yapılar, farklı türdeki nesneleri ancak bazı ortak veri veya işlemlerle işlemek için kullanılır. Normalde, bindirmenin temel yapısının (ortak kısım), her nesne türü için farklı bir rutin kümesine işaret eden bir işlev tablosuna işaretçisi vardır. C ++ aynı şeyi yapar ancak mekanizmaları gizler, yani ptr->func(...)işlev, C'nin olduğu gibi sanal olduğu C ++ (*ptr->func_table[func_num])(ptr,...), türetilmiş sınıflar arasındaki değişikliklerin func_table içeriği olduğu yerdir. [Sanal olmayan bir yöntem ptr-> func () sadece mangled_func (ptr, ..) anlamına gelir.]

Bunun sonucu, türetilmiş bir sınıfın yöntemlerini çağırmak için sadece temel sınıfı anlamanız gerektiğidir, yani bir rutin A sınıfını anlıyorsa, buna türetilmiş bir B sınıfı işaretçi iletebilirsiniz. B yerine A fonksiyonunu gösterir.


8

Sanal anahtar kelimesi derleyiciye erken bağlama yapmaması gerektiğini söyler. Bunun yerine, geç bağlama gerçekleştirmek için gerekli tüm mekanizmaları otomatik olarak kurmalıdır. Bunu yapmak için, tipik derleyici1 sanal işlevler içeren her sınıf için tek bir tablo (VTABLE olarak adlandırılır) oluşturur. Derleyici, söz konusu sınıf için sanal işlevlerin adreslerini VTABLE'a yerleştirir. Sanal işlevlere sahip her sınıfta, gizlice vpointer (VPTR olarak kısaltılır) adı verilen ve bu nesne için VTABLE'a işaret eden bir işaretçi yerleştirir. Bir temel sınıf işaretçisi aracılığıyla sanal işlev çağrısı yaptığınızda, derleyici sessizce VPTR'yi almak ve VTABLE'daki işlev adresini aramak için kodu ekler, böylece doğru işlevi çağırır ve geç bağlamanın gerçekleşmesine neden olur.

Bu bağlantıda daha fazla ayrıntı http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html


7

Sanal anahtar kelime kuvvetleri derleyici tanımlanan yöntem uygulanmasını almaya nesnenin sınıfın yerine de işaretçinin sınıfında.

Shape *shape = new Triangle(); 
cout << shape->getName();

Yukarıdaki örnekte, getName (), Base sınıfı Shape'de sanal olarak tanımlanmadığı sürece, varsayılan olarak Shape :: getName çağrılır. Bu, derleyiciyi Shape sınıfından ziyade Triangle sınıfında getName () uygulamasını aramaya zorlar.

Sanal tablo derleyici alt sınıflarına ait çeşitli sanal metodu uygulamaları takip ettiği bir mekanizmadır. Bu aynı zamanda dinamik bir gönderme olarak adlandırılır ve orada olduğunu olduğunu onunla ilişkili bazı havai.

Son olarak, neden C ++ 'da sanal bile gereklidir, neden Java'daki gibi varsayılan davranış yapmıyorsunuz?

  1. C ++, "Sıfır Ek yük" ve "Kullandığınız kadar ödeyin" ilkelerini temel alır. Bu nedenle, ihtiyacınız olmadığı sürece sizin için dinamik gönderim gerçekleştirmeye çalışmaz.
  2. Arayüz üzerinde daha fazla kontrol sağlamak. Bir işlevi sanal olmayan yaparak arabirim / soyut sınıfı davranışı tüm uygulamalarında denetleyebilir.

4

Neden sanal işlevlere ihtiyacımız var?

Sanal işlevler gereksiz tipleme sorununu önler ve bazılarımız türetilmiş sınıfa özgü işlevi çağırmak için türetilmiş sınıf işaretçisini kullanabildiğimizde neden sanal işlevlere ihtiyacımız olduğunu tartışabiliriz! geliştirme, burada tek işaretçi temel sınıf nesnesi olması çok arzu edilir.

Sanal işlevlerin önemini anlamak için aşağıdaki iki basit programı karşılaştıralım:

Sanal fonksiyonları olmayan program:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

ÇIKTI:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

Sanal fonksiyonlu program:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

ÇIKTI:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

Her iki çıktıyı da yakından analiz ederek sanal fonksiyonların önemini anlayabiliriz.


4

OOP Cevabı: Alt Tip Polimorfizmi

C ++ 'da, tanımı wikipedia'dan uygularsanız, polimorfizmi , daha kesin olarak alt tipleme veya alt tip polimorfizmi gerçekleştirmek için sanal yöntemlere ihtiyaç vardır .

Wikipedia, Subtyping, 2019-01-09: Programlama dili teorisinde, alt tipleme (ayrıca alt tip polimorfizm veya inklüzyon polimorfizmi), bir alt tipin başka bir veri tipiyle (üst tip) ilgili bazı kavramlarla ilişkili bir veri tipi olduğu bir tür polimorfizm türüdür. ikame edilebilirlik, yani süpertipin elementleri üzerinde çalışmak için yazılan program elemanlarının, tipik olarak alt rutinler veya fonksiyonlar, alt tipin elementleri üzerinde de çalışabilir.

NOT: Alt tür temel sınıf, alt tür ise miras alınan sınıf anlamına gelir.

Tipi Polimorfizm ile ilgili ileri bilgiler

Teknik Cevap: Dinamik Dağıtım

Bir temel sınıfa bir işaretçiniz varsa, yöntem çağrısı (sanal olarak bildirilir), oluşturulan nesnenin gerçek sınıfının yöntemine gönderilir. Bu Tip Polimorfizmi ++ C gerçekleştirilmiştir.

C ++ ve Dinamik Dağıtımdaki Polimorfizm Okuma

Uygulama Yanıtı : Vtable girişi oluşturur

Yöntemlerdeki "sanal" her değiştirici için, C ++ derleyicileri genellikle yöntemin bildirildiği sınıfın vtable'ında bir girdi oluşturur. C ++ derleyicisinin Dinamik Gönderimi gerçekleştirmesi budur .

Daha fazla okuma vtables


Örnek Kod

#include <iostream>

using namespace std;

class Animal {
public:
    virtual void MakeTypicalNoise() = 0; // no implementation needed, for abstract classes
    virtual ~Animal(){};
};

class Cat : public Animal {
public:
    virtual void MakeTypicalNoise()
    {
        cout << "Meow!" << endl;
    }
};

class Dog : public Animal {
public:
    virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
        cout << "Woof!" << endl;
    }
};

class Doberman : public Dog {
public:
    virtual void MakeTypicalNoise() {
        cout << "Woo, woo, woow!";
        cout << " ... ";
        Dog::MakeTypicalNoise();
    }
};

int main() {

    Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };

    const   int cnAnimals = sizeof(apObject)/sizeof(Animal*);
    for ( int i = 0; i < cnAnimals; i++ ) {
        apObject[i]->MakeTypicalNoise();
    }
    for ( int i = 0; i < cnAnimals; i++ ) {
        delete apObject[i];
    }
    return 0;
}

Örnek Kod Çıkışı

Meow!
Woof!
Woo, woo, woow! ... Woof!

Kod örneğinin UML sınıf diyagramı

Kod örneğinin UML sınıf diyagramı


1
Benim oyumu al, çünkü polimorfizmin belki de en önemli kullanımını gösteriyorsunuz: Sanal üye işlevlerine sahip bir temel sınıfın bir arabirimi veya başka bir deyişle bir API'yi belirtmesi . (Burada: En dizi) bir koleksiyon tüm öğeleri tedavi edebilir: (ana işlevi burada) bu tür bir sınıf çerçeve çalışması kullanarak kod düzgün ve gerekmez, istemiyor, ve aslında çoğu olamaz çağrılır somut hangi uygulama bilmek çalışma zamanında, örneğin henüz mevcut olmadığı için. Bu, nesneler ve işleyiciler arasında soyut ilişkiler kurmanın temellerinden biridir.
Peter - Monica'yı eski

2

İşte sanal yöntemin neden kullanıldığını gösteren eksiksiz bir örnek.

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}

1

Verimlilik hakkında, sanal işlevler erken bağlama işlevlerinden biraz daha az verimlidir.

"Bu sanal çağrı mekanizması neredeyse" normal işlev çağrısı "mekanizması (% 25 dahilinde) kadar verimli hale getirilebilir. Uzay yükü, sanal işlevlere sahip bir sınıfın her nesnesinin yanı sıra her bir sınıf için bir vtbl olan bir göstericidir" [ A Bjarne Stroustrup tarafından C ++ turu ]


2
Geç bağlama yalnızca işlev çağrısını yavaşlatmakla kalmaz, aynı zamanda çağrılan işlevi çalışma süresine kadar bilinmez yapar, bu nedenle işlev çağrısı boyunca optimizasyonlar uygulanamaz. Bu, her şeyi değiştirebilir f.ex. değer yayılımının çok fazla kodu kaldırdığı durumlarda ( if(param1>param2) return cst;derleyicinin tüm işlev çağrısını bazı durumlarda sabit tutabileceği yeri düşünün ).
curiousguy

1

Arayüz tasarımında sanal yöntemler kullanılmaktadır. Örneğin Windows'ta IUnknown adında bir arayüz var:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

Bu yöntemler uygulanacak arabirim kullanıcısına bırakılır. Bilinmeyenleri miras alması gereken belirli nesnelerin yaratılması ve yok edilmesi için gereklidirler. Bu durumda çalışma zamanı üç yöntemin farkındadır ve bunları çağırdığında bunların uygulanmasını bekler. Yani bir anlamda nesnenin kendisi ile o nesneyi kullanan her şey arasında bir sözleşme görevi görürler.


the run-time is aware of the three methods and expects them to be implementedSaf sanal olduklarından, bir örnek oluşturmanın bir yolu yoktur IUnknownve bu nedenle tüm alt sınıfların yalnızca derlemek için tüm bu yöntemleri uygulaması gerekir . Onları uygulamama ve sadece çalışma zamanında bulma tehlikesi yoktur (ama elbette biri onları yanlış uygulayabilir!). Ve vay, bugün Windows #definesa makrosu kelimesini öğrendim interface, muhtemelen kullanıcıları (A) Iadındaki öneki göremiyor veya (B) bir arayüz olduğunu görmek için sınıfa bakamıyor. Ugh
underscore_d

1

Ben bir yöntem sanal ilan kez gerçeğe başvuruyor düşünüyorum geçersiz kılmalar 'sanal' anahtar kelime kullanmak gerekmez.

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

Base'nin foo bildiriminde 'sanal' kullanmazsanız, Derived'in foo sadece gölgelendirir.


1

İlk iki cevap için C ++ kodunun birleştirilmiş versiyonu.

#include        <iostream>
#include        <string>

using   namespace       std;

class   Animal
{
        public:
#ifdef  VIRTUAL
                virtual string  says()  {       return  "??";   }
#else
                string  says()  {       return  "??";   }
#endif
};

class   Dog:    public Animal
{
        public:
                string  says()  {       return  "woof"; }
};

string  func(Animal *a)
{
        return  a->says();
}

int     main()
{
        Animal  *a = new Animal();
        Dog     *d = new Dog();
        Animal  *ad = d;

        cout << "Animal a says\t\t" << a->says() << endl;
        cout << "Dog d says\t\t" << d->says() << endl;
        cout << "Animal dog ad says\t" << ad->says() << endl;

        cout << "func(a) :\t\t" <<      func(a) <<      endl;
        cout << "func(d) :\t\t" <<      func(d) <<      endl;
        cout << "func(ad):\t\t" <<      func(ad)<<      endl;
}

İki farklı sonuç:

#Define virtual olmadan , derleme zamanında bağlanır. Animal * ad ve func (Animal *), Animal'in says () yöntemine işaret eder.

$ g++ virtual.cpp -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  ??
func(a) :       ??
func(d) :       ??
func(ad):       ??

#Define virtual ile çalışma zamanında bağlanır. Köpek * d, Hayvan * reklam ve func (Hayvan *) işaret / Köpeğin nesne türü olduğu için Köpeğin says () yöntemine bakın. [Dog's says () "woof"] yöntemi tanımlanmadığı sürece, sınıf ağacında ilk aranan yöntem olacaktır, yani türetilmiş sınıflar temel sınıflarının yöntemlerini geçersiz kılabilir [Animal's says ()].

$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

Python'daki tüm sınıf özelliklerinin (veriler ve yöntemler) etkili bir şekilde sanal olduğunu belirtmek ilginçtir . Tüm nesneler çalışma zamanında dinamik olarak oluşturulduğundan, tür bildirimi veya sanal anahtar kelimeye ihtiyaç yoktur. Python'un kod sürümü aşağıdadır:

class   Animal:
        def     says(self):
                return  "??"

class   Dog(Animal):
        def     says(self):
                return  "woof"

def     func(a):
        return  a.says()

if      __name__ == "__main__":

        a = Animal()
        d = Dog()
        ad = d  #       dynamic typing by assignment

        print("Animal a says\t\t{}".format(a.says()))
        print("Dog d says\t\t{}".format(d.says()))
        print("Animal dog ad says\t{}".format(ad.says()))

        print("func(a) :\t\t{}".format(func(a)))
        print("func(d) :\t\t{}".format(func(d)))
        print("func(ad):\t\t{}".format(func(ad)))

Çıktı:

Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

C ++ 'ın sanal tanımlaması ile aynıdır. Not bu d ve reklam aynı köpek örneğine işaret eden iki ayrı farklı değişken ibresi / vardır. (Reklam d) ifadesi True değerini döndürür ve değerleri 0xb79f72cc'deki aynı < main .Dog nesnesidir>.


1

İşlev işaretçileri hakkında bilgi sahibi misiniz? Verileri sanal işlevlere (sınıf üyeleri olarak) kolayca bağlayabilmeniz dışında, sanal işlevler benzer bir fikirdir. Verileri işlev işaretleyicilerine bağlamak o kadar kolay değildir. Bana göre bu temel kavramsal ayrım. Burada bir çok cevap sadece "çünkü ... polimorfizm!"


0

"Çalışma zamanı polimorfizmini" desteklemek için sanal yöntemlere ihtiyacımız var. İşaretçi veya temel sınıfa başvuru kullanarak türetilmiş bir sınıf nesnesine başvurduğunuzda, o nesne için bir sanal işlevi çağırabilir ve türetilmiş sınıfın işlev sürümünü yürütebilirsiniz.


-1

Sonuçta sanal işlevler hayatı kolaylaştırıyor. M Perry'nin bazı fikirlerini kullanalım ve sanal işlevlerimiz olmasaydı ve bunun yerine üye işlev işaretçileri kullanabilseydik ne olacağını açıklayalım. Sanal işlevler olmadan normal tahminimizde:

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
  };

 class derived: public base {
 public:
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main () {
      base hwOne;
      derived hwTwo = new derived();
      base->helloWorld(); //prints "Hello World!"
      derived->helloWorld(); //prints "Hello World!"

Tamam, bildiğimiz bu. Şimdi üye işlevi işaretçileriyle yapmaya çalışalım:

 #include <iostream>
 using namespace std;

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
 };

 class derived : public base {
 public:
 void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
 void(derived::*hwBase)();
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main()
 {
 base* b = new base(); //Create base object
 b->helloWorld(); // Hello World!
 void(derived::*hwBase)() = &derived::helloWorld; //create derived member 
 function pointer to base function
 derived* d = new derived(); //Create derived object. 
 d->displayHWDerived(hwBase); //Greetings World!

 char ch;
 cin >> ch;
 }

Üye işlevi işaretçileriyle bazı şeyler yapabilirken, sanal işlevler kadar esnek değillerdir. Bir sınıfta üye işlev işaretçisi kullanmak zor; üye-işlev işaretçisi neredeyse, en azından benim uygulamamda, her zaman yukarıdaki örnekte olduğu gibi ana işlevde veya üye işlevi içinden çağrılmalıdır.

Öte yandan, sanal işlevler, bazı işlev işaretçisi yükleri olsa da, işleri önemli ölçüde basitleştirir.

EDIT: eddietree ile benzer başka bir yöntem var: c ++ sanal işlevi vs üye işlev işaretçisi (performans karşılaştırma) .

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.