Pertanyaan Mengapa kita membutuhkan fungsi virtual di C ++?


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

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

Tapi sebelumnya di buku ini, ketika belajar tentang warisan dasar, saya bisa mengesampingkan fungsi dasar di kelas turunan tanpa menggunakan virtual.

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


965
2018-03-06 07:10


asal


Jawaban:


Di sini adalah bagaimana saya mengerti bukan hanya apa virtual fungsinya, tetapi mengapa mereka 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, kan? Hewan makan makanan generik, kucing makan tikus, semuanya tanpa virtual.

Mari kita ubah sedikit sekarang juga eat() dipanggil melalui fungsi perantara (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 berikan kucing func(), tapi itu tidak memakan tikus. Haruskah Anda membebani func() jadi dibutuhkan a Cat*? Jika Anda harus mendapatkan lebih banyak hewan dari Hewan, mereka semua akan membutuhkan hewan mereka sendiri func().

Solusinya adalah membuat eat() dari Animal kelas 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


2286
2018-03-06 13:54



Tanpa "virtual" Anda mendapatkan "pengikatan awal". Implementasi metode mana yang digunakan akan ditentukan pada waktu kompilasi berdasarkan jenis pointer yang Anda hubungi.

Dengan "virtual" Anda mendapatkan "terlambat mengikat". Implementasi metode apa yang digunakan akan diputuskan pada waktu proses berdasarkan jenis objek yang diarahkan ke tujuan - apa yang awalnya dibuat sebagai. Ini belum tentu apa yang Anda pikirkan berdasarkan jenis pointer yang mengarah 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 pada C ++.


533
2018-03-06 07:56



Anda membutuhkan setidaknya 1 tingkat pewarisan dan kemunduran untuk mendemonstrasikannya. Berikut ini 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

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

72
2018-03-06 07:26



Anda perlu metode virtual untuk downcasting yang aman, kesederhanaan dan keringkasan yg padat isinya.

Itulah yang dilakukan oleh metode maya: mereka berhenti dengan aman, dengan kode yang tampaknya sederhana dan ringkas, menghindari gips manual yang tidak aman dalam kode yang lebih kompleks dan verbose yang seharusnya Anda miliki.


Metode non-virtual ⇒ mengikat statis

Kode berikut ini sengaja "salah". Itu tidak menyatakan value metode 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 itu dikomentari sebagai "buruk" Expression::value metode dipanggil, karena tipe yang dikenal secara statis (tipe yang dikenal pada saat kompilasi) adalah Expression, dan value metode tidak virtual.


Metode virtual ⇒ mengikat dinamis.

Mendeklarasikan value sebagai virtual dalam tipe yang dikenal statis Expression memastikan bahwa setiap panggilan akan memeriksa jenis objek aktual apa ini, dan memanggil penerapan yang relevan valueuntuk itu tipe dinamis:

#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 sebagaimana mestinya, karena metode virtualnya disebut virtual. Ini juga disebut mengikat dinamis panggilan. Pemeriksaan kecil dilakukan, menemukan jenis objek dinamis yang sebenarnya, dan penerapan metode yang relevan untuk jenis dinamis tersebut, dipanggil.

Implementasi yang relevan adalah yang paling spesifik (sebagian besar berasal) kelas.

Perhatikan bahwa penerapan metode dalam kelas turunan di sini tidak ditandai virtual, tetapi malah ditandai override. Mereka bisa ditandai virtual tetapi mereka otomatis virtual. Itu override kata kunci memastikan bahwa jika ada tidak metode virtual seperti itu di beberapa kelas dasar, maka Anda akan mendapatkan kesalahan (yang diinginkan).


Keburukan melakukan ini tanpa metode virtual

Tanpa virtual seseorang harus mengimplementasikan beberapa Lakukan sendiri versi pengikatan dinamis. Ini yang umumnya melibatkan downcasting, kompleksitas, dan verbositas manual yang tidak aman.

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

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

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


28
2017-11-24 07:24



Jika kelas dasarnya Base, dan kelas turunan adalah Der, Anda dapat memiliki Base *p pointer yang sebenarnya menunjuk ke sebuah instance dari Der. Ketika Anda menelepon p->foo();, jika foo aku s tidak virtual, lalu Baseversi itu dieksekusi, mengabaikan fakta itu p sebenarnya menunjuk ke a Der. Jika foo aku s virtual, p->foo() mengeksekusi "paling" override dari foo, sepenuhnya mempertimbangkan kelas sebenarnya dari item yang diarahkan. Jadi perbedaan antara virtual dan non-virtual sebenarnya cukup penting: yang pertama memungkinkan runtime polimorfisme, konsep inti dari pemrograman OO, sedangkan yang kedua tidak.


27
2018-03-06 07:27



Kebutuhan Fungsi Virtual dijelaskan [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();

}

Outputnya adalah:

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

}

Outputnya adalah:

Hello from Class B.

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


24
2017-12-12 11:56



Fungsi Virtual digunakan untuk mendukung Polimorfisme Runtime.

Itu adalah, virtual kata kunci memberitahu compiler tidak membuat keputusan (fungsi yang mengikat) pada waktu kompilasi, agak menundanya untuk runtime ".

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

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

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

  • Ketika fungsi virtual dipanggil dengan menggunakan penunjuk kelas Basis, kompiler memutuskan pada waktu-run yang mana versi dari fungsi - yaitu versi kelas Base atau versi kelas Derived yang diganti - harus dipanggil. Ini disebut Polimorfisme Runtime.

23
2017-10-12 09:41



Anda harus membedakan antara overriding dan overloading. Tanpa virtual kata kunci Anda hanya membebani metode kelas dasar. Ini tidak berarti apa-apa selain bersembunyi. Katakanlah Anda memiliki kelas dasar Base dan kelas turunan Specialized yang keduanya menerapkan void foo(). Sekarang Anda memiliki pointer ke Base menunjuk ke sebuah instance dari Specialized. Ketika Anda menelepon foo() di atasnya Anda bisa mengamati perbedaan itu virtual membuat: Jika metodenya adalah virtual, implementasi dari Specialized akan digunakan, jika tidak ada, versi dari Base akan dipilih. Ini adalah praktik terbaik untuk tidak pernah membebani metode dari kelas dasar. Membuat metode non-virtual adalah cara penulisnya untuk memberi tahu Anda bahwa ekstensi dalam subclass tidak dimaksudkan.


19
2018-03-06 07:27



Mengapa kita membutuhkan Metode Virtual di C ++?

Jawaban cepat:

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

Dalam Bjarne Stroustrup C ++ Programming: Prinsip dan Praktik, (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 disebut ketika pengguna memanggil fungsi kelas dasar. Itu sering disebut polimorfisme run-time, pengiriman dinamis, atau waktu pengiriman karena fungsi yang dipanggil ditentukan pada waktu berjalan berdasarkan jenis objek yang digunakan.

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

Untuk menangani panggilan virtual, seseorang membutuhkan satu atau lebih data yang terkait dengan objek yang diturunkan  3. Cara yang biasanya dilakukan adalah menambahkan alamat tabel fungsi. Tabel ini biasanya disebut sebagai meja virtual atau tabel fungsi virtual dan alamatnya sering disebut pointer virtual. Setiap fungsi virtual mendapat slot di tabel virtual. Bergantung pada jenis objek pemanggil (turunan), fungsi virtual, pada gilirannya, akan memanggil penggantian masing-masing.


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

2. Anda tidak dapat mengodekan fungsionalitas menjadi lebih cepat atau menggunakan lebih sedikit memori menggunakan fitur bahasa lain untuk memilih di antara alternatif pada waktu proses. Bjarne Stroustrup C ++ Programming: Prinsip dan Praktik. (14.3.1).

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


14
2017-09-27 09:37



Saya ingin menambahkan penggunaan lain dari fungsi Virtual meskipun menggunakan konsep yang sama seperti jawaban yang disebutkan di atas, tetapi saya kira itu layak disebutkan.

DESTRUKTOR VIRTUAL

Pertimbangkan program ini di bawah ini, tanpa mendeklarasikan destruktor kelas Base 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

13
2018-03-02 09:44



Ketika Anda memiliki fungsi di kelas dasar, Anda bisa Redefine atau Override di kelas turunan.

Mendefinisikan kembali metode


12
2018-02-06 08:29