Mengapa kita membutuhkan fungsi virtual di C ++?


1312

Saya sedang belajar C ++ dan saya baru saja masuk ke fungsi virtual.

Dari apa yang saya baca (dalam buku dan online), fungsi virtual adalah fungsi dalam kelas dasar yang dapat Anda timpa dalam kelas turunan.

Tetapi sebelumnya dalam buku ini, ketika belajar tentang pewarisan dasar, saya bisa mengesampingkan fungsi dasar di kelas turunan tanpa menggunakan virtual.

Jadi apa yang saya lewatkan di sini? Saya tahu ada lebih banyak fungsi virtual, dan sepertinya penting jadi saya ingin menjelaskan apa itu sebenarnya. Saya tidak dapat menemukan jawaban langsung secara online.


13
Saya telah membuat penjelasan praktis untuk fungsi virtual di sini: nrecursions.blogspot.in/2015/06/...
Nav

4
Ini mungkin manfaat terbesar dari fungsi virtual - kemampuan untuk menyusun kode Anda sedemikian rupa sehingga kelas-kelas yang baru diturunkan akan secara otomatis bekerja dengan kode lama tanpa modifikasi!
user3530616

tbh, fungsi virtual adalah fitur pokok OOP, untuk penghapusan tipe. Saya pikir, ini adalah metode non-virtual yang membuat Object Pascal dan C ++ spesial, menjadi optimasi vtable besar yang tidak perlu dan memungkinkan kelas yang kompatibel dengan POD. Banyak bahasa OOP berharap bahwa setiap metode dapat diganti.
Swift - Friday Pie

Ini pertanyaan yang bagus. Memang hal virtual dalam C ++ ini akan diabstraksikan dalam bahasa lain seperti Java atau PHP. Dalam C ++ Anda hanya mendapatkan sedikit lebih banyak kontrol untuk beberapa kasus yang jarang terjadi (Waspadai beberapa warisan atau kasus khusus dari DDOD ). Tetapi mengapa pertanyaan ini diposting di stackoverflow.com?
Edgar Alloro

Saya pikir jika Anda melihat pada awal-mengikat mengikat dan VTABLE itu akan lebih masuk akal dan masuk akal. Jadi ada penjelasan yang bagus ( learncpp.com/cpp-tutorial/125-the-virtual-table ) di sini.
ceyun

Jawaban:


2729

Berikut adalah cara saya memahami bukan hanya virtualfungsi apa , tetapi mengapa itu diperlukan:

Katakanlah Anda memiliki dua kelas ini:

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

Dalam fungsi utama Anda:

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

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

Sejauh ini bagus, bukan? Hewan makan makanan generik, kucing makan tikus, semuanya tanpa virtual.

Mari kita ubah sedikit sekarang sehingga eat()dipanggil melalui fungsi antara (fungsi sepele hanya untuk contoh ini):

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

Sekarang fungsi utama kami adalah:

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 ... kami melewati Kucing func(), tetapi tidak mau makan tikus. Haruskah Anda kelebihan func()sehingga dibutuhkan Cat*? Jika Anda harus mendapatkan lebih banyak hewan dari Hewan, mereka semua akan membutuhkannya sendiri func().

Solusinya adalah membuat eat()dari Animalkelas fungsi virtual:

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

Utama:

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

Selesai


165
Jadi jika saya memahami ini dengan benar, virtual memungkinkan metode subclass dipanggil, bahkan jika objek tersebut diperlakukan sebagai superclassnya?
Kenny Worden

147
Alih-alih menjelaskan keterlambatan mengikat melalui contoh fungsi perantara "func", berikut adalah demonstrasi yang lebih langsung - Animal * animal = new Animal; // Kucing * kucing = Kucing baru; Hewan * kucing = Kucing baru; hewan-> makan (); // output: "Saya makan makanan generik." cat-> eat (); // output: "Saya makan makanan generik." Meskipun Anda menugaskan objek subclass (Cat), metode yang dipanggil didasarkan pada jenis pointer (Hewan) bukan jenis objek yang ditunjuknya. Inilah sebabnya mengapa Anda perlu "virtual".
rexbelia

37
Apakah saya satu-satunya yang menemukan perilaku default di C ++ ini aneh? Saya akan mengharapkan kode tanpa "virtual" bekerja.
David 天宇 Wong

20
@ David 天宇 Wong Saya pikir virtualmemperkenalkan beberapa binding dinamis vs statis dan ya itu aneh jika Anda berasal dari bahasa seperti Java.
peterchaula

32
Pertama-tama, panggilan virtual jauh, jauh lebih mahal daripada panggilan fungsi biasa. Filosofi C ++ cepat secara default, jadi panggilan virtual secara default adalah no-no besar. Alasan kedua adalah bahwa panggilan virtual dapat menyebabkan Anda melanggar kode jika Anda mewarisi kelas dari perpustakaan dan itu mengubah implementasi internal dari metode publik atau pribadi (yang memanggil metode virtual secara internal) tanpa mengubah perilaku kelas dasar.
Saolof

672

Tanpa "virtual" Anda mendapatkan "ikatan awal". Implementasi metode mana yang digunakan akan diputuskan pada waktu kompilasi berdasarkan jenis pointer yang Anda panggil.

Dengan "virtual" Anda mendapatkan "keterlambatan mengikat". Implementasi metode mana yang digunakan akan diputuskan pada saat run time berdasarkan jenis objek runcing - apa yang awalnya dibangun sebagai. Ini belum tentu apa yang Anda pikirkan berdasarkan jenis pointer yang menunjuk ke objek itu.

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"

EDIT - lihat pertanyaan ini .

Juga - tutorial ini mencakup pengikatan awal dan akhir dalam C ++.


11
Luar biasa, dan sampai di rumah dengan cepat dan dengan menggunakan contoh-contoh yang lebih baik. Namun ini sederhana, dan si penanya harus benar-benar membaca halaman parashift.com/c++-faq-lite/virtual-functions.html . Orang lain sudah menunjuk sumber ini dalam artikel SO yang ditautkan dari utas ini, tapi saya percaya ini layak disebutkan kembali.
Sonny

36
Saya tidak tahu apakah ikatan awal dan akhir adalah istilah yang khusus digunakan dalam komunitas c ++, tetapi istilah yang benar adalah statis (pada waktu kompilasi) dan ikatan dinamis (saat runtime).
mike

31
@mike - "Istilah" pengikatan yang terlambat "berasal dari setidaknya tahun 1960-an, di mana ia dapat ditemukan di Komunikasi ACM." . Bukankah lebih baik jika ada satu kata yang tepat untuk setiap konsep? Sayangnya, itu tidak benar. Istilah "penjilidan awal" dan "penjilidan akhir" mendahului C ++ dan bahkan pemrograman berorientasi objek, dan sama benarnya dengan istilah yang Anda gunakan.
Steve314

4
@ BJovke - jawaban ini ditulis sebelum C ++ 11 diterbitkan. Meski begitu, saya hanya mengkompilasinya di GCC 6.3.0 (menggunakan C ++ 14 secara default) tanpa masalah - jelas membungkus deklarasi variabel dan memanggil dalam mainfungsi dll. Pointer-to-diturunkan secara implisit dilemparkan ke pointer-to-base (lebih khusus secara implisit dilemparkan ke yang lebih umum). Visa-versa Anda memerlukan pemeran eksplisit, biasanya a dynamic_cast. Hal lain - sangat rentan terhadap perilaku yang tidak terdefinisi jadi pastikan Anda tahu apa yang Anda lakukan. Sepengetahuan saya, ini tidak berubah sejak sebelum bahkan C ++ 98.
Steve314

10
Perhatikan bahwa kompiler C ++ saat ini sering dapat mengoptimalkan terlambat ke awal mengikat - ketika mereka dapat memastikan apa yang akan menjadi mengikat. Ini juga disebut sebagai "de-virtualisasi".
einpoklum

83

Anda membutuhkan setidaknya 1 level warisan dan orang yang tertunduk untuk menunjukkannya. Ini adalah contoh yang sangat sederhana:

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
Contoh Anda mengatakan bahwa string yang dikembalikan tergantung pada apakah fungsi tersebut virtual, tetapi tidak mengatakan hasil mana yang sesuai dengan virtual dan yang sesuai dengan non-virtual. Selain itu, ini sedikit membingungkan karena Anda tidak menggunakan string yang dikembalikan.
Ross

7
Dengan kata kunci Virtual: Pakan . Tanpa kata kunci Virtual: ? .
Hesham Eraqi

@HeshamEraqi tanpa virtual mengikat awal dan itu akan menampilkan "?" kelas dasar
Ahmad

46

Anda memerlukan metode virtual untuk downcasting yang aman , kesederhanaan dan keringkasan .

Itulah yang dilakukan metode virtual: mereka ditelusuri dengan aman, dengan kode yang tampaknya sederhana dan ringkas, menghindari gips manual yang tidak aman dalam kode yang lebih rumit dan bertele-tele yang seharusnya Anda miliki.


Metode non-virtual binding penjilidan statis

Kode berikut sengaja "salah". Itu tidak menyatakan valuemetode sebagai virtual, dan karena itu menghasilkan hasil "salah" yang tidak diinginkan, yaitu 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;
}

Dalam baris yang dikomentari sebagai "buruk" Expression::valuemetode ini disebut, karena tipe yang dikenal secara statis (tipe yang dikenal pada waktu kompilasi) adalah Expression, dan valuemetode ini tidak virtual.


Metode virtual binding penjilidan dinamis.

Menyatakan valuesebagaivirtual pada tipe yang dikenal secara statis Expressionmemastikan bahwa setiap panggilan akan memeriksa apa jenis objek yang sebenarnya, dan memanggil implementasi yang relevan valueuntuk tipe dinamis itu :

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

Di sini hasilnya 6.86 seperti yang seharusnya, karena metode virtual disebut secara virtual . Ini juga disebut pengikatan dinamis dari panggilan. Pemeriksaan kecil dilakukan, menemukan tipe dinamis objek yang sebenarnya, dan implementasi metode yang relevan untuk tipe dinamis itu, disebut.

Implementasi yang relevan adalah yang ada di kelas yang paling spesifik (paling diturunkan).

Perhatikan bahwa implementasi metode dalam kelas turunan di sini tidak ditandai virtual, melainkan ditandai override. Mereka dapat ditandai virtualtetapi secara otomatis virtual. Ituoverride Memastikan kata kunci bahwa jika ada tidak seperti metode virtual di beberapa kelas dasar, maka Anda akan mendapatkan error (yang diinginkan).


Keburukan melakukan ini tanpa metode virtual

Tanpa virtual harus menerapkan beberapa versi Do It Yourself dari ikatan dinamis. Inilah yang umumnya melibatkan downcasting manual yang tidak aman, kompleksitas dan verbositas.

Untuk kasus fungsi tunggal, seperti di sini, sudah cukup untuk menyimpan pointer fungsi di objek dan memanggil melalui pointer fungsi, tetapi meskipun demikian itu melibatkan beberapa downcasts yang tidak aman, kompleksitas dan verbosity, untuk dikata:

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

Salah satu cara positif untuk melihat ini adalah, jika Anda menemukan downcasting yang tidak aman, kompleksitas dan verbositas seperti di atas, maka seringkali metode virtual atau metode dapat sangat membantu.


40

Fungsi Virtual digunakan untuk mendukung Polimorfisme Runtime .

Artinya, kata kunci virtual memberi tahu kompiler untuk tidak membuat keputusan (mengikat fungsi) pada waktu kompilasi, agak menunda untuk runtime " .

  • Anda dapat membuat fungsi virtual dengan mendahului kata kunci virtualdalam deklarasi kelas dasar. Sebagai contoh,

     class Base
     {
        virtual void func();
     }
  • Ketika Kelas Basis memiliki fungsi anggota virtual, setiap kelas yang mewarisi dari Kelas Basis dapat mendefinisikan kembali fungsi dengan prototipe yang sama persis yaitu fungsi hanya dapat didefinisikan ulang, bukan antarmuka fungsi.

     class Derive : public Base
     {
        void func();
     }
  • Pointer kelas basis dapat digunakan untuk menunjuk ke objek kelas dasar serta objek kelas turunan.

  • Ketika fungsi virtual dipanggil dengan menggunakan pointer kelas Base, kompiler memutuskan pada saat run-time versi fungsi mana - yaitu versi kelas Base atau versi kelas turunan yang ditimpa - harus dipanggil. Ini disebut Runtime Polymorphism .

34

Jika kelas dasar adalah Base, dan kelas turunannya Der, Anda dapat memiliki Base *ppointer yang menunjuk ke instance dari Der. Ketika Anda memanggil p->foo();, jika fooini tidak virtual, maka Base's versi dijalankan, mengabaikan fakta bahwa psebenarnya menunjuk ke sebuah Der. Jika foo adalah virtual, p->foo()mengeksekusi "leafmost" override dari foo, sepenuhnya memperhitungkan kelas yang sebenarnya dari item runcing-to. Jadi perbedaan antara virtual dan non-virtual sebenarnya cukup penting: yang pertama memungkinkan polimorfisme runtime , konsep inti dari pemrograman OO, sedangkan yang terakhir tidak.


8
Saya benci untuk menentang Anda, tetapi menyusun waktu polimorfisme masih polimorfisme. Bahkan kelebihan fungsi non-anggota adalah bentuk polimorfisme - polimorfisme ad-hoc menggunakan terminologi dalam tautan Anda. Perbedaannya di sini adalah antara mengikat awal dan akhir.
Steve314

7
@ Steve314, Anda benar pedantically (sebagai sesama pedant, saya menyetujuinya ;-) - mengedit jawaban untuk menambahkan kata sifat yang hilang ;-).
Alex Martelli

26

Need for Virtual Function menjelaskan [Mudah dimengerti]

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

}

Output akan menjadi:

Hello from Class A.

Tetapi dengan fungsi virtual:

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

}

Output akan menjadi:

Hello from Class B.

Oleh karena itu dengan fungsi virtual Anda dapat mencapai polimorfisme runtime.


25

Saya ingin menambahkan lagi penggunaan fungsi Virtual meskipun ia menggunakan konsep yang sama dengan jawaban yang dinyatakan di atas, tetapi saya rasa itu layak disebutkan.

DESTRUKTOR VIRTUAL

Pertimbangkan program ini di bawah, tanpa mendeklarasikan destruktor kelas basis sebagai virtual; memori untuk Cat mungkin tidak dibersihkan.

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

Keluaran:

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

Keluaran:

Deleting an Animal name Cat
Deleting an Animal

11
without declaring Base class destructor as virtual; memory for Cat may not be cleaned up.Lebih buruk dari itu. Menghapus objek yang diturunkan melalui basis pointer / referensi adalah perilaku murni yang tidak terdefinisi. Jadi, bukan hanya sebagian memori yang bocor. Sebaliknya, program ini sakit-terbentuk, sehingga compiler dapat mengubahnya menjadi apa saja: kode mesin yang terjadi untuk bekerja dengan baik, atau tidak apa-apa, atau panggilan setan dari hidung Anda, atau dll Itu sebabnya, jika program didesain sedemikian cara beberapa pengguna dapat menghapus turunan turunan melalui basis referensi, basis tersebut harus memiliki destruktor virtual
underscore_d

21

Anda harus membedakan antara overriding dan overloading. Tanpa virtualkata kunci, Anda hanya membebani metode kelas dasar. Ini tidak lain berarti bersembunyi. Katakanlah Anda memiliki kelas dasar Basedan kelas turunan Specializedyang keduanya mengimplementasikan void foo(). Sekarang Anda memiliki pointer untuk Basemenunjuk ke instance Specialized. Ketika Anda memanggilnya foo(), Anda dapat mengamati perbedaan yang virtualmembuat: Jika metode ini virtual, implementasi Specializedakan digunakan, jika tidak ada, versi dari Baseakan dipilih. Ini adalah praktik terbaik untuk tidak pernah membebani metode dari kelas dasar. Membuat metode non-virtual adalah cara penulisnya memberi tahu Anda bahwa ekstensi dalam subkelas tidak dimaksudkan.


3
Tanpa virtualAnda, Anda tidak kelebihan beban. Kamu membayangi . Jika kelas dasar Bmemiliki satu fungsi atau lebih foo, dan kelas turunan Dmendefinisikan foonama, yang foo menyembunyikan semua foo-s di dalamnya B. Mereka tercapai karena B::foomenggunakan resolusi lingkup. Untuk mempromosikan B::foofungsi menjadi Dkelebihan beban, Anda harus menggunakan using B::foo.
Kaz

20

Mengapa kita membutuhkan Metode Virtual di C ++?

Jawaban cepat:

  1. Ini memberi kita salah satu "bahan" 1 yang dibutuhkan untuk pemrograman berorientasi objek .

Dalam Bjarne Stroustrup, C ++ Programming: Principles and Practice, (14.3):

Fungsi virtual menyediakan kemampuan untuk mendefinisikan fungsi dalam kelas dasar dan memiliki fungsi dengan nama dan jenis yang sama dalam kelas turunan yang dipanggil ketika pengguna memanggil fungsi kelas dasar. Itu sering disebut run-time polymorphism , dynamic dispatch , atau run-time dispatch karena fungsi yang dipanggil ditentukan pada saat run time berdasarkan jenis objek yang digunakan.

  1. Ini adalah implementasi tercepat yang lebih efisien jika Anda memerlukan panggilan fungsi virtual 2 .

Untuk menangani panggilan virtual, seseorang memerlukan satu atau lebih data yang terkait dengan objek yang diturunkan 3 . Cara yang biasanya dilakukan adalah menambahkan alamat dari daftar fungsi. Tabel ini biasanya disebut sebagai tabel virtual atau tabel fungsi virtual dan alamatnya sering disebut virtual pointer . Setiap fungsi virtual mendapatkan slot di tabel virtual. Bergantung pada tipe objek (yang diturunkan) dari penelepon, fungsi virtual, pada gilirannya, memanggil masing-masing override.


1.Penggunaan pewarisan, run-time polymorphism, dan enkapsulasi adalah definisi paling umum dari pemrograman berorientasi objek .

2. Anda tidak dapat membuat kode fungsi menjadi lebih cepat atau menggunakan lebih sedikit memori menggunakan fitur bahasa lain untuk memilih di antara alternatif pada waktu berjalan. Bjarne Stroustrup C ++ Pemrograman: Prinsip dan Praktek. (14.3.1) .

3. Sesuatu untuk mengetahui fungsi mana yang benar-benar dipanggil ketika kita memanggil kelas dasar yang berisi fungsi virtual.


15

Saya sudah jawab saya dalam bentuk percakapan untuk menjadi lebih baik membaca:


Mengapa kita membutuhkan fungsi virtual?

Karena Polimorfisme.

Apa itu Polimorfisme?

Fakta bahwa pointer basis juga dapat menunjuk ke objek tipe turunan.

Bagaimana definisi Polimorfisme mengarah pada kebutuhan akan fungsi virtual?

Nah, melalui penjilidan awal .

Apa yang mengikat awal?

Pengikatan awal (pengikatan waktu kompilasi) dalam C ++ berarti pemanggilan fungsi diperbaiki sebelum program dijalankan.

Begitu...?

Jadi jika Anda menggunakan tipe dasar sebagai parameter fungsi, kompiler hanya akan mengenali antarmuka dasar, dan jika Anda memanggil fungsi itu dengan argumen apa pun dari kelas turunan, itu akan dipotong, yang bukan apa yang Anda inginkan terjadi.

Jika bukan itu yang kita inginkan terjadi, mengapa ini dibolehkan?

Karena kita membutuhkan Polimorfisme!

Apa manfaat Polimorfisme?

Anda dapat menggunakan pointer tipe dasar sebagai parameter dari fungsi tunggal, dan kemudian pada saat run-time dari program Anda, Anda dapat mengakses setiap antarmuka tipe turunan (mis. Fungsi anggota mereka) tanpa masalah, menggunakan dereferencing dari single itu pointer dasar.

Saya masih tidak tahu apa fungsi virtual yang bagus untuk ...! Dan ini pertanyaan pertamaku!

baik, ini karena Anda terlalu cepat bertanya!

Mengapa kita membutuhkan fungsi virtual?

Asumsikan bahwa Anda memanggil fungsi dengan basis pointer, yang memiliki alamat objek dari salah satu kelas turunannya. Seperti yang telah kita bahas di atas, dalam run-time, pointer ini mengalami dereferensi, sejauh ini bagus, namun, kami mengharapkan metode (== fungsi anggota) "dari kelas turunan kami" akan dieksekusi! Namun, metode yang sama (yang memiliki header yang sama) sudah didefinisikan di kelas dasar, jadi mengapa program Anda harus repot-repot memilih metode lain? Dengan kata lain yang saya maksud, bagaimana Anda bisa membedakan skenario ini dari apa yang biasanya kita lihat terjadi sebelumnya?

Jawaban singkatnya adalah "fungsi anggota Virtual di basis", dan jawaban yang sedikit lebih lama adalah bahwa, "pada langkah ini, jika program melihat fungsi virtual di kelas dasar, ia tahu (menyadari) bahwa Anda sedang mencoba untuk menggunakan polimorfisme "dan seterusnya pergi ke kelas turunan (menggunakan v-table , suatu bentuk pengikatan akhir) untuk menemukan metode lain dengan header yang sama, tetapi dengan - tanpa diduga - implementasi yang berbeda.

Mengapa implementasi berbeda?

Anda kepala menyerah! Baca buku yang bagus !

OK, tunggu tunggu tunggu, mengapa orang repot-repot menggunakan pointer basis, ketika dia hanya bisa menggunakan pointer jenis turunan? Anda menjadi hakim, apakah semua sakit kepala ini sepadan? Lihatlah dua cuplikan ini:

// 1:

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

// 2:

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

OK, walaupun saya pikir 1 masih lebih baik dari 2 , Anda bisa menulis 1 seperti ini juga:

// 1:

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

dan terlebih lagi, Anda harus menyadari bahwa ini belum menggunakan semua hal yang telah saya jelaskan kepada Anda sejauh ini. Alih-alih ini, anggap sebagai contoh situasi di mana Anda memiliki fungsi dalam program Anda yang menggunakan metode dari masing-masing kelas turunan masing-masing (getMonthBenefit ()):

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

Sekarang, coba tulis ulang ini, tanpa sakit kepala!

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

Dan sebenarnya, ini mungkin juga contoh yang dibuat-buat!


2
konsep iterasi pada berbagai jenis objek (sub-) menggunakan tipe objek tunggal (super-) harus disorot, itu poin bagus yang Anda berikan, terima kasih
harshvchawla

14

Ketika Anda memiliki fungsi di kelas dasar, Anda bisa Redefineatau Overrideitu di kelas turunan.

Mendefinisikan ulang metode : Implementasi baru untuk metode kelas dasar diberikan dalam kelas turunan. Tidak memfasilitasiDynamic binding.

Mengganti metode : Redefiningavirtual methoddari kelas dasar di kelas turunan. Metode virtual memfasilitasi Pengikatan Dinamis .

Jadi, ketika Anda berkata:

Tetapi sebelumnya dalam buku ini, ketika belajar tentang pewarisan dasar, saya bisa mengganti metode dasar dalam kelas turunan tanpa menggunakan 'virtual'.

Anda tidak menimpanya karena metode di kelas dasar bukan virtual, melainkan Anda mendefinisikannya kembali


11

Ini membantu jika Anda mengetahui mekanisme yang mendasarinya. C ++ memformalkan beberapa teknik pengkodean yang digunakan oleh programmer C, "kelas" diganti dengan menggunakan "overlay" - struct dengan bagian header umum akan digunakan untuk menangani objek dari berbagai jenis tetapi dengan beberapa data umum atau operasi. Biasanya struct dasar overlay (bagian umum) memiliki pointer ke tabel fungsi yang menunjuk ke set rutin yang berbeda untuk setiap jenis objek. C ++ melakukan hal yang sama tetapi menyembunyikan mekanisme yaitu C ++ di ptr->func(...)mana func adalah virtual seperti C(*ptr->func_table[func_num])(ptr,...) , di mana perubahan antara kelas turunan adalah konten func_table. [Metode ptr-> func () non-virtual hanya diterjemahkan ke mangled_func (ptr, ..).]

Hasilnya adalah bahwa Anda hanya perlu memahami kelas dasar untuk memanggil metode kelas turunan, yaitu jika suatu rutin memahami kelas A, Anda dapat meneruskannya dengan kelas B penunjuk maka metode virtual yang dipanggil akan menjadi B daripada A karena Anda pergi melalui tabel fungsi B menunjuk pada.


8

Kata kunci virtual memberi tahu kompiler bahwa ia seharusnya tidak melakukan pengikatan awal. Sebagai gantinya, itu harus secara otomatis menginstal semua mekanisme yang diperlukan untuk melakukan ikatan yang terlambat. Untuk mencapai hal ini, compiler1 khas membuat tabel tunggal (disebut VTABLE) untuk setiap kelas yang berisi fungsi virtual. Kompiler menempatkan alamat fungsi virtual untuk kelas tertentu di VTABLE. Di setiap kelas dengan fungsi virtual, itu diam-diam menempatkan pointer, yang disebut vpointer (disingkat VPTR), yang menunjuk ke VTABLE untuk objek itu. Ketika Anda membuat panggilan fungsi virtual melalui pointer kelas dasar, kompiler diam-diam memasukkan kode untuk mengambil VPTR dan mencari alamat fungsi di VTABLE, sehingga memanggil fungsi yang benar dan menyebabkan keterlambatan terjadi.

Lebih detail dalam tautan ini http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html


7

Kata kunci virtual memaksa kompiler untuk memilih implementasi metode yang didefinisikan dalam kelas objek daripada di kelas pointer .

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

Dalam contoh di atas, Shape :: getName akan dipanggil secara default, kecuali getName () didefinisikan sebagai virtual dalam bentuk kelas Base. Ini memaksa kompiler untuk mencari implementasi getName () di kelas Triangle daripada di kelas Shape.

The tabel virtual adalah mekanisme di mana compiler terus melacak dari berbagai implementasi-metode virtual dari subclass. Ini juga disebut dispatch dinamis, dan ada yang beberapa overhead yang terkait dengan itu.

Akhirnya, mengapa virtual bahkan diperlukan di C ++, mengapa tidak menjadikannya perilaku default seperti di Jawa?

  1. C ++ didasarkan pada prinsip "Zero Overhead" dan "Pay for what you use". Jadi itu tidak mencoba melakukan pengiriman dinamis untuk Anda, kecuali jika Anda membutuhkannya.
  2. Untuk memberikan lebih banyak kontrol ke antarmuka. Dengan membuat fungsi non-virtual, antarmuka / kelas abstrak dapat mengontrol perilaku di semua implementasinya.

4

Mengapa kita membutuhkan fungsi virtual?

Fungsi virtual menghindari masalah typecasting yang tidak perlu, dan beberapa dari kita dapat berdebat bahwa mengapa kita memerlukan fungsi virtual ketika kita dapat menggunakan pointer kelas turunan untuk memanggil fungsi spesifik di kelas turunan! Jawabannya adalah - itu membatalkan seluruh gagasan warisan dalam sistem besar pengembangan, di mana memiliki objek kelas dasar pointer tunggal sangat diinginkan.

Mari kita bandingkan dua program sederhana di bawah ini untuk memahami pentingnya fungsi virtual:

Program tanpa fungsi virtual:

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

KELUARAN:

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

Program dengan fungsi virtual:

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

KELUARAN:

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

Dengan menganalisis kedua output secara cermat, orang dapat memahami pentingnya fungsi virtual.


4

OOP Jawab: Subtipe Polimorfisme

Dalam C ++, metode virtual diperlukan untuk mewujudkan polimorfisme , lebih tepatnya subtipe atau polimorfisme subtipe jika Anda menerapkan definisi dari wikipedia.

Wikipedia, Subtyping, 2019-01-09: Dalam teori bahasa pemrograman, subtyping (juga subtipe polimorfisme atau polimorfisme inklusi) adalah bentuk polimorfisme tipe di mana subtipe adalah tipe data yang terkait dengan tipe data lain (supertipe) oleh beberapa gagasan substitusi, artinya elemen program, biasanya subrutin atau fungsi, ditulis untuk beroperasi pada elemen supertipe juga dapat beroperasi pada elemen subtipe.

CATATAN: Subtipe berarti kelas dasar, dan subtipe berarti kelas bawaan.

Bacaan lebih lanjut tentang Subtipe Polimorfisme

Jawaban Teknis: Pengiriman Dinamis

Jika Anda memiliki pointer ke kelas dasar, maka panggilan metode (yang dinyatakan sebagai virtual) akan dikirim ke metode kelas aktual objek yang dibuat. Ini adalah bagaimana Subtipe Polimorfisme direalisasikan adalah C ++.

Bacaan lebih lanjut Polimorfisme dalam C ++ dan Dynamic Dispatch

Jawaban Implementasi: Membuat entri vtable

Untuk setiap pengubah "virtual" pada metode, kompiler C ++ biasanya membuat entri dalam vtable kelas di mana metode tersebut dideklarasikan. Ini adalah bagaimana kompiler C ++ umum mewujudkan Dynamic Dispatch .

Bacaan lanjutan vtables


Kode Contoh

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

Output dari Kode Contoh

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

Diagram kelas kode contoh UML

Diagram kelas kode contoh UML


1
Ambil upvote saya karena Anda menunjukkan kemungkinan penggunaan polimorfisme yang paling penting: Bahwa kelas dasar dengan fungsi anggota virtual menentukan antarmuka atau, dengan kata lain, API. Kode yang menggunakan kerangka kerja kelas seperti ini (di sini: fungsi utama Anda) dapat memperlakukan semua item dalam koleksi (di sini: array Anda) secara seragam dan tidak perlu, tidak mau, dan memang sering kali tidak dapat mengetahui implementasi konkret mana yang akan dijalankan saat dijalankan, misalnya karena belum ada. Ini adalah salah satu dasar dari ukiran hubungan abstrak antara objek dan penangan.
Peter - Pasang kembali Monica

2

Berikut ini adalah contoh lengkap yang menggambarkan mengapa metode virtual digunakan.

#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

Tentang efisiensi, fungsi virtual sedikit kurang efisien sebagai fungsi pengikatan awal.

"Mekanisme panggilan virtual ini dapat dibuat hampir seefisien mekanisme" panggilan fungsi normal "(dalam 25%). Overhead ruangnya adalah satu penunjuk di setiap objek kelas dengan fungsi virtual plus satu vtbl untuk setiap kelas tersebut" [ A tour C ++ oleh Bjarne Stroustrup]


2
Pengikatan yang terlambat tidak hanya membuat pemanggilan fungsi lebih lambat, itu membuat fungsi yang dipanggil tidak diketahui hingga waktu berjalan, jadi optimisasi di seluruh pemanggilan fungsi tidak dapat diterapkan. Ini dapat mengubah segalanya f.ex. dalam kasus di mana propagasi nilai menghapus banyak kode (pikirkan di if(param1>param2) return cst;mana kompiler dapat mengurangi seluruh panggilan fungsi ke konstanta dalam beberapa kasus).
curiousguy

1

Metode virtual digunakan dalam desain antarmuka. Misalnya di Windows ada antarmuka bernama IUnknown seperti di bawah ini:

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

Metode-metode ini diserahkan kepada pengguna antarmuka untuk diimplementasikan. Mereka sangat penting untuk penciptaan dan penghancuran objek tertentu yang harus mewarisi IUnknown. Dalam hal ini run-time mengetahui tiga metode dan mengharapkannya untuk diimplementasikan ketika memanggilnya. Jadi dalam arti mereka bertindak sebagai kontrak antara objek itu sendiri dan apa pun yang menggunakan objek itu.


the run-time is aware of the three methods and expects them to be implementedKarena mereka adalah virtual murni, tidak ada cara untuk membuat instance IUnknown, dan semua subclass harus mengimplementasikan semua metode tersebut untuk hanya mengkompilasi. Tidak ada bahaya jika tidak mengimplementasikannya dan hanya menemukan itu pada saat runtime (tapi tentu saja orang bisa salah mengimplementasikannya, tentu saja!). Dan wow, hari ini saya belajar Windows #definesa macro dengan kata itu interface, mungkin karena pengguna mereka tidak bisa hanya (A) melihat awalan Idalam nama atau (B) melihat kelas untuk melihat itu sebuah antarmuka. Ugh
underscore_d

1

saya pikir Anda merujuk pada fakta setelah suatu metode dinyatakan virtual Anda tidak perlu menggunakan kata kunci 'virtual' dalam menimpa.

class Base { virtual void foo(); };

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

Jika Anda tidak menggunakan 'virtual' dalam deklarasi foo Pangkalan maka foo Derived akan membayangi saja.


1

Berikut ini adalah versi gabungan dari kode C ++ untuk dua jawaban pertama.

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

Dua hasil berbeda adalah:

Tanpa #define virtual , ia mengikat pada waktu kompilasi. Animal * iklan dan func (Hewan *) semua mengarah ke metode mengatakan () Hewan.

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

Dengan #define virtual , ia mengikat saat dijalankan. Dog * d, Animal * iklan dan func (Animal *) menunjuk / merujuk pada metode Dog's said () karena Dog adalah tipe objek mereka. Kecuali jika [Dog's said () "woof"] metode tidak didefinisikan, itu akan menjadi yang pertama dicari di pohon kelas, yaitu kelas turunan dapat menimpa metode kelas dasar mereka [Animal's said ()].

$ 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

Sangat menarik untuk dicatat bahwa semua atribut kelas (data dan metode) dalam Python efektif secara virtual . Karena semua objek dibuat secara dinamis saat runtime, tidak ada deklarasi tipe atau kebutuhan untuk kata kunci virtual. Di bawah ini adalah versi kode Python:

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

Outputnya adalah:

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

yang identik dengan mendefinisikan virtual C ++. Perhatikan bahwa d dan iklan adalah dua variabel penunjuk berbeda yang merujuk / menunjuk ke turunan Dog yang sama. Ekspresi (iklan adalah d) mengembalikan True dan nilainya adalah < objek .dog utama yang sama di 0xb79f72cc>


1

Apakah Anda terbiasa dengan pointer fungsi? Fungsi virtual adalah ide yang serupa, kecuali Anda dapat dengan mudah mengikat data ke fungsi virtual (sebagai anggota kelas). Tidak mudah untuk mengikat data ke pointer fungsi. Bagi saya, ini adalah perbedaan konsep utama. Banyak jawaban lain di sini hanya mengatakan "karena ... polimorfisme!"


0

Kami membutuhkan metode virtual untuk mendukung "Jalankan Polimorfisme Waktu". Saat Anda merujuk ke objek kelas turunan menggunakan pointer atau referensi ke kelas dasar, Anda bisa memanggil fungsi virtual untuk objek itu dan menjalankan versi fungsi kelas turunan.


-1

Intinya adalah bahwa fungsi virtual membuat hidup lebih mudah. Mari kita gunakan beberapa ide M Perry dan menjelaskan apa yang akan terjadi jika kita tidak memiliki fungsi virtual dan sebagai gantinya hanya dapat menggunakan pointer fungsi anggota. Kami miliki, dalam estimasi normal tanpa fungsi virtual:

 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!"

Ok, jadi itu yang kita tahu. Sekarang mari kita coba melakukannya dengan pointer fungsi anggota:

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

Meskipun kita dapat melakukan beberapa hal dengan pointer fungsi anggota, mereka tidak sefleksibel fungsi virtual. Sulit untuk menggunakan pointer fungsi anggota di kelas; pointer fungsi anggota hampir, paling tidak dalam praktik saya, selalu harus dipanggil di fungsi utama atau dari dalam fungsi anggota seperti dalam contoh di atas.

Di sisi lain, fungsi virtual, sementara mereka mungkin memiliki beberapa fungsi-pointer overhead, melakukan penyederhanaan hal-hal secara dramatis.

EDIT: Ada metode lain yang mirip dengan eddietree: c ++ fungsi virtual vs fungsi penunjuk anggota (perbandingan kinerja) .

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.