Tidak ada cinta C ++ dalam hal "fitur tersembunyi" pertanyaan? Kupikir aku akan membuangnya ke sana. Apa sajakah fitur tersembunyi dari C ++?
Tidak ada cinta C ++ dalam hal "fitur tersembunyi" pertanyaan? Kupikir aku akan membuangnya ke sana. Apa sajakah fitur tersembunyi dari C ++?
Jawaban:
Kebanyakan programmer C ++ sudah familiar dengan operator terner:
x = (y < 0) ? 10 : 20;
Namun, mereka tidak menyadari bahwa ini dapat digunakan sebagai nilai l:
(a == 0 ? a : b) = 1;
yang merupakan singkatan dari
if (a == 0)
a = 1;
else
b = 1;
Gunakan dengan hati-hati :-)
(value ? function1 : function2)()
.
function1
dan function2
secara implisit dikonversi ke pointer fungsi, dan hasilnya secara implisit diubah kembali.
Anda dapat menempatkan URI ke dalam sumber C ++ tanpa kesalahan. Sebagai contoh:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
, yang memang dimiliki C ++). Apa pun setelah dua garis miring adalah sebuah komentar. Oleh karena itu, dengan http://stackoverflow.com
, http
adalah label (Anda secara teoritis dapat menulis goto http;
), dan //stackoverflow.com
hanya komentar akhir baris. Keduanya adalah C ++ legal, sehingga konstruk dikompilasi. Itu tidak melakukan sesuatu yang samar-samar berguna, tentu saja.
goto http;
sebenarnya tidak mengikuti URL. :(
Aritmatika penunjuk.
Pemrogram C ++ lebih suka menghindari petunjuk karena bug yang dapat diperkenalkan.
C ++ paling keren yang pernah saya lihat? Literal analog.
Saya setuju dengan sebagian besar posting di sana: C ++ adalah bahasa multi-paradigma, jadi fitur "tersembunyi" yang akan Anda temukan (selain "perilaku tidak terdefinisi" yang harus Anda hindari dengan cara apa pun) adalah penggunaan fasilitas yang cerdas.
Sebagian besar fasilitas tersebut bukanlah fitur bawaan bahasa, tetapi yang berbasis perpustakaan.
Yang paling penting adalah RAII , sering diabaikan selama bertahun-tahun oleh pengembang C ++ yang berasal dari dunia C. Kelebihan beban operator sering kali merupakan fitur yang disalahpahami yang memungkinkan perilaku seperti array (operator subskrip), operasi seperti penunjuk (penunjuk cerdas), dan operasi serupa build-in (mengalikan matriks.
Penggunaan pengecualian sering kali sulit, tetapi dengan beberapa pekerjaan, dapat menghasilkan kode yang sangat kuat melalui spesifikasi keselamatan pengecualian (termasuk kode yang tidak akan gagal, atau yang akan memiliki fitur seperti commit yang akan berhasil, atau kembali ke keadaan aslinya).
Fitur "tersembunyi" yang paling terkenal dari C ++ adalah metaprogramming template , karena fitur ini memungkinkan Anda menjalankan sebagian (atau seluruhnya) program Anda pada waktu kompilasi, bukan pada waktu proses. Ini sulit, dan Anda harus memiliki pemahaman yang kuat tentang templat sebelum mencobanya.
Orang lain menggunakan beberapa paradigma untuk menghasilkan "cara pemrograman" di luar leluhur C ++, yaitu C.
Dengan menggunakan functors , Anda dapat mensimulasikan fungsi, dengan keamanan tipe tambahan dan menjadi stateful. Dengan menggunakan pola perintah , Anda dapat menunda eksekusi kode. Kebanyakan pola desain lainnya dapat dengan mudah dan efisien diimplementasikan dalam C ++ untuk menghasilkan gaya pengkodean alternatif yang tidak seharusnya berada di dalam daftar "paradigma C ++ resmi".
Dengan menggunakan templat , Anda dapat menghasilkan kode yang akan berfungsi pada sebagian besar jenis, termasuk bukan yang Anda pikirkan pada awalnya. Anda juga dapat meningkatkan keamanan tipe (seperti malloc aman jenis otomatis / realloc / free). Fitur objek C ++ sangat kuat (dan karenanya, berbahaya jika digunakan secara sembarangan), tetapi polimorfisme dinamis pun memiliki versi statisnya di C ++: CRTP .
Saya telah menemukan bahwa sebagian besar buku jenis " C ++ Efektif " dari Scott Meyers atau buku jenis " C ++ Luar Biasa " dari Herb Sutter mudah dibaca, dan cukup berharga untuk info tentang fitur C ++ yang dikenal dan kurang dikenal.
Di antara pilihan saya adalah salah satu yang harus membuat rambut setiap programmer Java bangkit dari horor: Dalam C ++, cara paling berorientasi objek untuk menambahkan fitur ke objek adalah melalui fungsi non-anggota non-teman, daripada anggota- fungsi (yaitu metode kelas), karena:
Dalam C ++, antarmuka kelas adalah fungsi-anggota dan fungsi non-anggota dalam namespace yang sama
fungsi non-teman non-anggota tidak memiliki akses istimewa ke internal kelas. Dengan demikian, menggunakan fungsi anggota di atas non-anggota non-teman akan melemahkan enkapsulasi kelas.
Ini tidak pernah gagal untuk mengejutkan bahkan pengembang berpengalaman.
(Sumber: Antara lain, Guru online Herb Sutter minggu ini # 84: http://www.gotw.ca/gotw/084.htm )
Salah satu fitur bahasa yang saya anggap agak tersembunyi, karena saya belum pernah mendengarnya sepanjang waktu saya di sekolah, adalah alias namespace. Itu tidak menarik perhatian saya sampai saya menemukan contohnya di dokumentasi pendorong. Tentu saja, setelah saya mengetahuinya, Anda dapat menemukannya di referensi C ++ standar apa pun.
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
using
.
Variabel tidak hanya dapat dideklarasikan di bagian init dari sebuah for
loop, tetapi juga kelas dan fungsi.
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
Itu memungkinkan banyak variabel dengan jenis yang berbeda.
Operator array bersifat asosiatif.
A [8] adalah sinonim untuk * (A + 8). Karena penjumlahan bersifat asosiatif, itu dapat ditulis ulang sebagai * (8 + A), yang merupakan sinonim untuk ..... 8 [A]
Anda tidak mengatakan berguna ... :-)
A
tidak penting sama sekali. Misalnya, jika A
a char*
, kode tersebut akan tetap valid.
Satu hal yang sedikit diketahui adalah bahwa serikat pekerja juga dapat menjadi templat:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
Dan mereka juga dapat memiliki konstruktor dan fungsi anggota. Tidak ada yang ada hubungannya dengan warisan (termasuk fungsi virtual).
From
dan To
disetel serta digunakan dengan semestinya . Gabungan semacam itu dapat digunakan dengan perilaku yang ditentukan (dengan To
menjadi sebuah array dari unsigned char atau struct berbagi urutan awal dengan From
). Meskipun Anda menggunakannya dengan cara yang tidak ditentukan, ini mungkin masih berguna untuk pekerjaan tingkat rendah. Bagaimanapun, ini hanyalah salah satu contoh template serikat - mungkin ada kegunaan lain untuk serikat yang memiliki template.
C ++ adalah standar, seharusnya tidak ada fitur tersembunyi ...
C ++ adalah bahasa multi-paradigma, Anda dapat mempertaruhkan uang terakhir Anda karena ada fitur tersembunyi. Satu contoh dari banyak: metaprogramming template . Tak seorang pun di komite standar bermaksud agar ada sub-bahasa Turing-complete yang dijalankan pada waktu kompilasi.
Fitur tersembunyi lainnya yang tidak berfungsi di C adalah fungsionalitas unary +
operator . Anda dapat menggunakannya untuk mempromosikan dan merusak segala macam hal
+AnEnumeratorValue
Dan nilai pencacah Anda yang sebelumnya memiliki tipe pencacahan sekarang memiliki tipe bilangan bulat sempurna yang sesuai dengan nilainya. Secara manual, Anda tidak akan tahu tipe itu! Ini diperlukan misalnya ketika Anda ingin menerapkan operator yang kelebihan beban untuk enumerasi Anda.
Anda harus menggunakan kelas yang menggunakan penginisialisasi statis dalam kelas tanpa definisi di luar kelas, tetapi terkadang gagal untuk menautkan? Operator dapat membantu membuat sementara tanpa membuat asumsi atau ketergantungan pada tipenya
struct Foo {
static int const value = 42;
};
// This does something interesting...
template<typename T>
void f(T const&);
int main() {
// fails to link - tries to get the address of "Foo::value"!
f(Foo::value);
// works - pass a temporary value
f(+Foo::value);
}
Apakah Anda ingin meneruskan dua pointer ke suatu fungsi, tetapi tidak berhasil? Operator mungkin membantu
// This does something interesting...
template<typename T>
void f(T const& a, T const& b);
int main() {
int a[2];
int b[3];
f(a, b); // won't work! different values for "T"!
f(+a, +b); // works! T is "int*" both time
}
Masa hidup temporer yang terikat pada referensi konst adalah salah satu yang hanya diketahui sedikit orang. Atau setidaknya itu adalah pengetahuan C ++ favorit saya yang tidak diketahui kebanyakan orang.
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
Fitur bagus yang jarang digunakan adalah blok coba-tangkap di seluruh fungsi:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
Penggunaan utama akan menerjemahkan pengecualian ke kelas pengecualian lain dan memutar ulang, atau untuk menerjemahkan antara pengecualian dan penanganan kode kesalahan berbasis kembali.
return
menangkap blok dari Coba Fungsi, hanya lempar ulang.
Banyak yang tahu tentang identity
/ id
metafunction, tetapi ada kasus penggunaan yang bagus untuknya untuk kasus non-template: Kemudahan menulis deklarasi:
// void (*f)(); // same
id<void()>::type *f;
// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);
// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];
// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;
Ini sangat membantu mendekripsi dekripsi C ++!
// boost::identity is pretty much the same
template<typename T>
struct id { typedef T type; };
template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;
-> pointer<function<void,int>> f(pointer<function<void,void>>);
atau pointer<void(int)> f(pointer<void()>);
ataufunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
Fitur yang cukup tersembunyi adalah Anda dapat mendefinisikan variabel dalam kondisi if, dan cakupannya hanya akan mencakup if, dan blok else:
if(int * p = getPointer()) {
// do something
}
Beberapa makro menggunakannya, misalnya untuk menyediakan beberapa cakupan "terkunci" seperti ini:
struct MutexLocker {
MutexLocker(Mutex&);
~MutexLocker();
operator bool() const { return false; }
private:
Mutex &m;
};
#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else
void someCriticalPath() {
locked(myLocker) { /* ... */ }
}
BOOST_FOREACH juga menggunakannya di bawah tenda. Untuk menyelesaikan ini, tidak hanya mungkin di jika, tetapi juga di sakelar:
switch(int value = getIt()) {
// ...
}
dan dalam beberapa saat:
while(SomeThing t = getSomeThing()) {
// ...
}
(dan juga dalam kondisi untuk). Tapi saya tidak terlalu yakin apakah ini semua berguna :)
if((a = f()) == b) ...
, tetapi jawaban ini sebenarnya menyatakan variabel dalam kondisi tersebut.
for(...; int i = foo(); ) ...;
This akan melewati body selama i
true, menginisialisasinya setiap kali lagi. Perulangan yang Anda perlihatkan hanya mendemonstrasikan deklarasi variabel, tetapi bukan deklarasi variabel yang secara bersamaan bertindak sebagai kondisi :)
Kadang-kadang Anda menggunakan operator koma yang valid, tetapi Anda ingin memastikan bahwa tidak ada operator koma yang ditentukan pengguna yang menghalangi, karena misalnya Anda mengandalkan titik urutan antara sisi kiri dan kanan atau ingin memastikan tidak ada yang mengganggu yang diinginkan tindakan. Di sinilah void()
masuk ke dalam permainan:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
Abaikan placeholder yang saya masukkan untuk kondisi dan kode. Yang penting adalah void()
, yang membuat kompilator memaksa untuk menggunakan operator koma bawaan. Ini bisa berguna saat mengimplementasikan kelas ciri, terkadang juga.
Inisialisasi array dalam konstruktor. Misalnya di kelas jika kita memiliki array int
sebagai:
class clName
{
clName();
int a[10];
};
Kita dapat menginisialisasi semua elemen dalam array ke default-nya (di sini semua elemen array menjadi nol) di konstruktor sebagai:
clName::clName() : a()
{
}
Oooh, saya bisa membuat daftar kebencian hewan peliharaan sebagai gantinya:
Di sisi positifnya
Anda dapat mengakses data yang dilindungi dan anggota fungsi dari kelas mana pun, tanpa perilaku yang tidak ditentukan, dan dengan semantik yang diharapkan. Baca terus untuk mengetahui caranya. Baca juga laporan kerusakan tentang ini.
Biasanya, C ++ melarang Anda untuk mengakses anggota kelas yang dilindungi non-statis, meskipun kelas itu adalah kelas dasar Anda.
struct A {
protected:
int a;
};
struct B : A {
// error: can't access protected member
static int get(A &x) { return x.a; }
};
struct C : A { };
Itu dilarang: Anda dan kompilator tidak tahu apa yang sebenarnya ditunjukkan oleh referensi tersebut. Ini bisa menjadi C
objek, di mana kelas B
tidak memiliki bisnis dan petunjuk tentang datanya. Akses tersebut hanya diberikan jika x
merupakan referensi ke kelas turunan atau turunan darinya. Dan itu dapat memungkinkan bagian kode yang berubah-ubah membaca setiap anggota yang dilindungi hanya dengan membuat kelas "membuang" yang membacakan anggota, misalnya std::stack
:
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
// error: stack<int>::c is protected
return s.c;
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Tentunya, seperti yang Anda lihat ini akan menyebabkan terlalu banyak kerusakan. Tapi sekarang, petunjuk anggota memungkinkan menghindari perlindungan ini! Poin kuncinya adalah bahwa jenis penunjuk anggota terikat ke kelas yang sebenarnya berisi anggota tersebut - bukan ke kelas yang Anda tentukan saat mengambil alamat. Ini memungkinkan kami untuk menghindari pemeriksaan
struct A {
protected:
int a;
};
struct B : A {
// valid: *can* access protected member
static int get(A &x) { return x.*(&B::a); }
};
struct C : A { };
Dan tentu saja, ini juga berfungsi dengan std::stack
contoh.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
return s.*(pillager::c);
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Itu akan menjadi lebih mudah dengan menggunakan deklarasi di kelas turunan, yang membuat nama anggota menjadi publik dan merujuk ke anggota kelas dasar.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
using std::stack<int>::c;
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = s.*(&pillager::c);
}
Fitur tersembunyi lainnya adalah Anda dapat memanggil objek kelas yang dapat diubah menjadi pointer atau referensi fungsi. Resolusi kelebihan beban dilakukan berdasarkan hasil dari mereka, dan argumen diteruskan dengan sempurna.
template<typename Func1, typename Func2>
class callable {
Func1 *m_f1;
Func2 *m_f2;
public:
callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
operator Func1*() { return m_f1; }
operator Func2*() { return m_f2; }
};
void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }
int main() {
callable<void(int), void(long)> c(foo, bar);
c(42); // calls foo
c(42L); // calls bar
}
Ini disebut "fungsi panggilan pengganti".
Fitur tersembunyi:
Jika suatu fungsi menampilkan pengecualian yang tidak tercantum dalam spesifikasi pengecualiannya, tetapi fungsi tersebut memiliki std::bad_exception
spesifikasi pengecualiannya, pengecualian tersebut diubah menjadi std::bad_exception
dan ditampilkan secara otomatis. Dengan cara itu Anda setidaknya akan tahu bahwa ada bad_exception
yang terlempar. Baca lebih lanjut di sini .
fungsi coba blok
Kata kunci template dalam membedakan typedefs di template kelas. Jika nama spesialisasi anggota template yang muncul setelah .
, ->
atau ::
operator, dan nama yang memiliki template parameter yang memenuhi syarat secara eksplisit, awalan nama template anggota dengan template kata kunci. Baca lebih lanjut di sini .
default parameter fungsi dapat diubah saat runtime. Baca lebih lanjut di sini .
A[i]
bekerja sebaik i[A]
Contoh sementara kelas dapat dimodifikasi! Fungsi anggota non-const dapat dipanggil pada objek sementara. Sebagai contoh:
struct Bar {
void modify() {}
}
int main (void) {
Bar().modify(); /* non-const function invoked on a temporary. */
}
Baca lebih lanjut di sini .
Jika ada dua tipe berbeda sebelum dan sesudah ekspresi operator :
in ternary ( ?:
), maka tipe ekspresi yang dihasilkan adalah yang paling umum dari keduanya. Sebagai contoh:
void foo (int) {}
void foo (double) {}
struct X {
X (double d = 0.0) {}
};
void foo (X) {}
int main(void) {
int i = 1;
foo(i ? 0 : 0.0); // calls foo(double)
X x;
foo(i ? 0.0 : x); // calls foo(X)
}
map::operator[]
membuat entri jika kunci hilang dan mengembalikan referensi ke nilai entri yang dibuat secara default. Jadi Anda bisa menulis:
map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
s.assign(...);
}
cout << s;
Saya kagum dengan banyaknya programmer C ++ yang tidak mengetahui hal ini.
.find()
.
const map::operator[]
menghasilkan pesan kesalahan"
Menempatkan fungsi atau variabel dalam namespace tanpa nama akan menghentikan penggunaan static
untuk membatasinya ke cakupan file.
static
dalam lingkup global sama sekali tidak ditinggalkan. (Untuk referensi: C ++ 03 §D.2)
static
penggunaan sebaiknya hanya digunakan di dalam tipe kelas atau fungsi.
Mendefinisikan fungsi teman biasa di template kelas membutuhkan perhatian khusus:
template <typename T>
class Creator {
friend void appear() { // a new function ::appear(), but it doesn't
… // exist until Creator is instantiated
}
};
Creator<void> miracle; // ::appear() is created at this point
Creator<double> oops; // ERROR: ::appear() is created a second time!
Dalam contoh ini, dua contoh berbeda menciptakan dua definisi yang identik — pelanggaran langsung terhadap ODR
Oleh karena itu, kita harus memastikan parameter templat dari templat kelas muncul dalam jenis fungsi teman yang ditentukan dalam templat itu (kecuali kami ingin mencegah lebih dari satu contoh templat kelas dalam file tertentu, tetapi ini agak tidak mungkin). Mari terapkan ini ke variasi dari contoh kita sebelumnya:
template <typename T>
class Creator {
friend void feed(Creator<T>*){ // every T generates a different
… // function ::feed()
}
};
Creator<void> one; // generates ::feed(Creator<void>*)
Creator<double> two; // generates ::feed(Creator<double>*)
Penafian: Saya telah menempelkan bagian ini dari Template C ++: Panduan Lengkap / Bagian 8.4
Sedikit diketahui, tetapi kode berikut baik-baik saja
void f() { }
void g() { return f(); }
Serta yang tampak aneh berikut ini
void f() { return (void)"i'm discarded"; }
Mengetahui hal ini, Anda dapat memanfaatkan di beberapa area. Salah satu contoh: void
fungsi tidak dapat mengembalikan nilai tetapi Anda juga tidak dapat hanya mengembalikan apa pun, karena fungsi tersebut dapat dibuat dengan non-void. Alih-alih menyimpan nilai ke dalam variabel lokal, yang akan menyebabkan kesalahan void
, cukup kembalikan nilai secara langsung
template<typename T>
struct sample {
// assume f<T> may return void
T dosomething() { return f<T>(); }
// better than T t = f<T>(); /* ... */ return t; !
};
Membaca file menjadi vektor string:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
- kurung hilang setelah param kedua
Anda dapat membuat template bitfield.
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
Saya belum menemukan tujuan apa pun untuk ini, tetapi itu pasti sangat mengejutkan saya.
Salah satu tata bahasa paling menarik dari semua bahasa pemrograman.
Tiga dari hal-hal ini menjadi satu, dan dua adalah sesuatu yang sama sekali berbeda ...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
Semua kecuali yang ketiga dan kelima menentukan SomeType
objek pada tumpukan dan menginisialisasinya (dengan u
dalam dua kasus pertama, dan konstruktor default pada kasus keempat. Yang ketiga adalah mendeklarasikan fungsi yang tidak mengambil parameter dan mengembalikan a SomeType
. Yang kelima mendeklarasikan dengan cara yang sama fungsi yang mengambil satu parameter dengan nilai tipe SomeType
bernama u
.
Singkirkan deklarasi maju:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
Menulis pernyataan switch dengan?: Operator:
string result =
a==0 ? "zero" :
a==1 ? "one" :
a==2 ? "two" :
0;
Melakukan semuanya dalam satu baris:
void a();
int b();
float c = (a(),b(),1.0f);
Mengosongkan struct tanpa memset:
FStruct s = {0};
Normalisasi / pembungkus nilai sudut dan waktu:
int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150
Menetapkan referensi:
struct ref
{
int& r;
ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
FStruct s = {};
bahkan lebih pendek.
main
? Saya sarankan global().main();
dan lupakan saja tentang singleton ( Anda bisa bekerja dengan yang sementara, yang diperpanjang seumur hidup )
Operator bersyarat terner ?:
membutuhkan operan kedua dan ketiga untuk memiliki tipe yang "menyenangkan" (berbicara secara informal). Tetapi persyaratan ini memiliki satu pengecualian (permainan kata-kata): operan kedua atau ketiga dapat berupa ekspresi lemparan (yang memiliki tipevoid
), terlepas dari jenis operan lainnya.
Dengan kata lain, seseorang dapat menulis ekspresi C ++ yang benar-benar valid menggunakan ?:
operator
i = a > b ? a : throw something();
BTW, fakta bahwa ekspresi throw sebenarnya adalah ekspresi (tipe void
) dan bukan pernyataan adalah fitur lain yang sedikit diketahui dari bahasa C ++. Artinya, antara lain, kode berikut ini benar-benar valid
void foo()
{
return throw something();
}
meskipun tidak ada gunanya melakukannya dengan cara ini (mungkin dalam beberapa kode template umum ini mungkin berguna).
Aturan dominasi berguna, tetapi sedikit diketahui. Dikatakan bahwa meskipun dalam jalur non-unik melalui kisi kelas dasar, pencarian nama untuk anggota yang sebagian tersembunyi adalah unik jika anggota tersebut termasuk dalam kelas dasar virtual:
struct A { void f() { } };
struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };
// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };
Saya telah menggunakan ini untuk mengimplementasikan dukungan-penyelarasan yang secara otomatis mengetahui penyelarasan paling ketat melalui aturan dominasi.
Ini tidak hanya berlaku untuk fungsi virtual, tetapi juga untuk nama typedef, anggota statis / non-virtual, dan lainnya. Saya pernah melihatnya digunakan untuk menerapkan sifat yang dapat ditimpa dalam program meta.
struct C
dalam contoh Anda ...? Bersulang.