Saya mendapat pertanyaan ini ketika saya menerima komentar ulasan kode yang mengatakan fungsi virtual tidak perlu sebaris.
Saya pikir fungsi virtual sebaris bisa berguna dalam skenario di mana fungsi dipanggil pada objek secara langsung. Namun argumen yang muncul di benak saya adalah - mengapa seseorang ingin mendefinisikan virtual dan kemudian menggunakan objek untuk memanggil metode?
Apakah lebih baik tidak menggunakan fungsi virtual sebaris, karena mereka hampir tidak pernah diperluas?
Cuplikan kode yang saya gunakan untuk analisis:
class Temp
{
public:
virtual ~Temp()
{
}
virtual void myVirtualFunction() const
{
cout<<"Temp::myVirtualFunction"<<endl;
}
};
class TempDerived : public Temp
{
public:
void myVirtualFunction() const
{
cout<<"TempDerived::myVirtualFunction"<<endl;
}
};
int main(void)
{
TempDerived aDerivedObj;
//Compiler thinks it's safe to expand the virtual functions
aDerivedObj.myVirtualFunction();
//type of object Temp points to is always known;
//does compiler still expand virtual functions?
//I doubt compiler would be this much intelligent!
Temp* pTemp = &aDerivedObj;
pTemp->myVirtualFunction();
return 0;
}
pTemp->myVirtualFunction()
bisa diselesaikan sebagai panggilan non-virtual, itu mungkin ada sebaris panggilan itu. Panggilan yang direferensikan ini diuraikan oleh g ++ 3.4.2:TempDerived & pTemp = aDerivedObj; pTemp.myVirtualFunction();
Kode Anda tidak.Jawaban:
Fungsi virtual kadang-kadang dapat diuraikan. Kutipan dari faq C ++ yang unggul :
sumber
C ++ 11 telah ditambahkan
final
. Ini mengubah jawaban yang diterima: tidak lagi perlu untuk mengetahui kelas yang tepat dari objek, itu cukup untuk mengetahui objek memiliki setidaknya tipe kelas di mana fungsi tersebut dinyatakan final:sumber
icc
sepertinya melakukannya, sesuai tautan itu.Ada satu kategori fungsi virtual di mana masih masuk akal untuk memilikinya inline. Pertimbangkan kasus berikut:
Panggilan untuk menghapus 'basis', akan melakukan panggilan virtual untuk memanggil destruktor kelas turunan yang benar, panggilan ini tidak inline. Namun karena setiap destructor menyebutnya induk destructor (yang dalam kasus ini kosong), kompilator dapat inline mereka panggilan, karena mereka tidak memanggil fungsi kelas dasar virtual.
Prinsip yang sama ada untuk konstruktor kelas dasar atau untuk setiap set fungsi di mana implementasi turunan juga memanggil implementasi kelas dasar.
sumber
Saya telah melihat kompiler yang tidak memancarkan v-table jika tidak ada fungsi non-inline sama sekali ada (dan didefinisikan dalam satu file implementasi, bukan header). Mereka akan melempar kesalahan seperti
missing vtable-for-class-A
atau sesuatu yang serupa, dan Anda akan bingung sekali, seperti saya.Memang, itu tidak sesuai dengan Standar, tetapi itu terjadi jadi pertimbangkan menempatkan setidaknya satu fungsi virtual tidak di header (jika hanya destruktor virtual), sehingga kompiler dapat memancarkan tabel untuk kelas di tempat itu. Saya tahu ini terjadi pada beberapa versi
gcc
.Seperti yang disebutkan seseorang, fungsi virtual sebaris kadang-kadang bisa bermanfaat , tetapi tentu saja paling sering Anda akan menggunakannya ketika Anda tidak tahu jenis objek yang dinamis, karena itulah alasan utama
virtual
di tempat pertama.Namun kompiler tidak dapat sepenuhnya diabaikan
inline
. Ini memiliki semantik lain selain mempercepat panggilan fungsi. The inline implisit definisi di kelas adalah mekanisme yang memungkinkan Anda untuk menempatkan definisi ke header: Hanyainline
fungsi dapat didefinisikan beberapa kali sepanjang seluruh program tanpa melanggar aturan. Pada akhirnya, itu berlaku karena Anda hanya akan mendefinisikannya sekali dalam seluruh program, meskipun Anda memasukkan header beberapa kali ke dalam file yang berbeda yang dihubungkan bersama.sumber
Sebenarnya fungsi virtual selalu dapat digarisbawahi , asalkan keduanya secara statis dihubungkan bersama: misalkan kita memiliki kelas abstrak
Base
dengan fungsi virtualF
dan kelas turunanDerived1
danDerived2
:Panggilan hipotetis
b->F();
(denganb
tipeBase*
) jelas virtual. Tetapi Anda (atau kompiler ...) dapat menulis ulang seperti itu (misalkantypeof
adalahtypeid
fungsi- like yang mengembalikan nilai yang dapat digunakan dalam aswitch
)sementara kita masih membutuhkan RTTI untuk itu
typeof
, panggilan secara efektif dapat digarisbawahi oleh, pada dasarnya, menanamkan vtable di dalam aliran instruksi dan mengkhususkan panggilan untuk semua kelas yang terlibat. Ini dapat digeneralisasikan dengan mengkhususkan hanya beberapa kelas (katakan saja, adilDerived1
):sumber
Menandai metode virtual sebaris, membantu lebih mengoptimalkan fungsi virtual dalam dua kasus berikut:
Pola template berulang yang aneh ( http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurr--Template-Patt )
Mengganti metode virtual dengan templat ( http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html )
sumber
inline benar-benar tidak melakukan apa-apa - itu adalah petunjuk. Kompilator mungkin mengabaikannya atau mungkin inline acara panggilan tanpa inline jika melihat implementasi dan menyukai ide ini. Jika kejelasan kode dipertaruhkan inline harus dihapus.
sumber
Fungsi Virtual yang dideklarasikan sebaris diuraikan saat dipanggil melalui objek dan diabaikan ketika dipanggil melalui pointer atau referensi.
sumber
Dengan kompiler modern, tidak ada salahnya untuk inlibe mereka. Beberapa combo compiler / linker kuno mungkin telah membuat beberapa vtables, tapi saya tidak percaya itu adalah masalah lagi.
sumber
Kompiler hanya dapat inline fungsi ketika panggilan dapat diselesaikan dengan jelas pada waktu kompilasi.
Fungsi virtual, bagaimanapun diselesaikan pada saat runtime, dan karenanya kompiler tidak dapat inline panggilan, karena pada kompilasi ketik tipe dinamis (dan karenanya implementasi fungsi yang akan dipanggil) tidak dapat ditentukan.
sumber
Dalam kasus-kasus di mana pemanggilan fungsi tidak ambigu dan fungsi sebagai kandidat yang cocok untuk inlining, kompiler cukup cerdas untuk tetap memasukkan kode.
Sisa waktu "virtual sebaris" adalah omong kosong, dan memang beberapa kompiler tidak akan mengkompilasi kode itu.
sumber
Masuk akal untuk membuat fungsi virtual dan kemudian memanggilnya pada objek daripada referensi atau pointer. Scott Meyer merekomendasikan, dalam bukunya "efektif c ++", untuk tidak pernah mendefinisikan kembali fungsi non-virtual yang diwarisi. Itu masuk akal, karena ketika Anda membuat kelas dengan fungsi non-virtual dan mendefinisikan kembali fungsi dalam kelas turunan, Anda mungkin yakin untuk menggunakannya dengan benar sendiri, tetapi Anda tidak bisa memastikan orang lain akan menggunakannya dengan benar. Juga, Anda di kemudian hari dapat menggunakannya secara salah sendiri. Jadi, jika Anda membuat fungsi di kelas dasar dan Anda ingin dapat didefinisikan ulang, Anda harus membuatnya virtual. Jika masuk akal untuk membuat fungsi virtual dan memanggilnya pada objek, masuk akal juga untuk menyelaraskannya.
sumber
Sebenarnya dalam beberapa kasus menambahkan "inline" ke virtual final override dapat membuat kode Anda tidak dikompilasi sehingga kadang-kadang ada perbedaan (setidaknya di bawah kompiler VS2017s)!
Sebenarnya saya sedang melakukan fungsi override akhir inline virtual dalam VS2017 menambahkan c ++ 17 standar untuk mengkompilasi dan menghubungkan dan untuk beberapa alasan gagal ketika saya menggunakan dua proyek.
Saya memiliki proyek pengujian dan implementasi DLL yang saya uji unit. Dalam proyek pengujian saya memiliki file "linker_includes.cpp" yang #include file * .cpp dari proyek lain yang diperlukan. Saya tahu ... Saya tahu saya dapat mengatur msbuild untuk menggunakan file objek dari DLL, tetapi harap diingat bahwa ini adalah solusi spesifik microsoft sementara menyertakan file cpp tidak terkait dengan build-system dan jauh lebih mudah untuk versi file cpp daripada file xml dan pengaturan proyek dan ...
Yang menarik adalah saya terus mendapatkan kesalahan linker dari proyek pengujian. Bahkan jika saya menambahkan definisi fungsi yang hilang oleh copy paste dan tidak melalui include! Sangat aneh. Proyek lain telah dibangun dan tidak ada hubungan antara keduanya selain menandai referensi proyek sehingga ada pesanan pembangunan untuk memastikan keduanya selalu dibangun ...
Saya pikir itu adalah semacam bug di kompiler. Saya tidak tahu apakah itu ada di kompiler yang dikirim dengan VS2020, karena saya menggunakan versi yang lebih lama karena beberapa SDK hanya berfungsi dengan benar :-(
Saya hanya ingin menambahkan bahwa tidak hanya menandai mereka sebagai inline dapat berarti sesuatu, tetapi bahkan mungkin membuat kode Anda tidak membangun dalam beberapa keadaan langka! Ini aneh, namun baik untuk diketahui.
PS .: Kode yang sedang saya kerjakan terkait dengan grafik komputer, jadi saya lebih suka inlining dan itulah sebabnya saya menggunakan final dan inline. Saya menyimpan specifier terakhir untuk berharap rilis rilis cukup pintar untuk membangun DLL dengan menggarisbawahi bahkan tanpa saya secara langsung mengisyaratkan jadi ...
PS (Linux) .: Saya berharap hal yang sama tidak terjadi di gcc atau dentang karena saya secara rutin melakukan hal-hal semacam ini. Saya tidak yakin dari mana masalah ini berasal ... Saya lebih suka melakukan c ++ di Linux atau setidaknya dengan beberapa gcc, tetapi terkadang proyek berbeda dalam kebutuhan.
sumber