Fitur Tersembunyi C ++? [Tutup]


114

Tidak ada cinta C ++ dalam hal "fitur tersembunyi" pertanyaan? Kupikir aku akan membuangnya ke sana. Apa sajakah fitur tersembunyi dari C ++?


@Devtron - Saya telah melihat beberapa bug yang luar biasa (yaitu perilaku yang tidak terduga) dijual sebagai fitur. Faktanya, industri game benar-benar mencoba untuk mewujudkannya saat ini dan menyebutnya "gameplay yang muncul" (juga, lihat "TK Surfing" dari Psi-Ops, murni bug, lalu mereka membiarkannya apa adanya dan itu salah satu fitur terbaik dari game IMHO)
Grant Peters

5
@ Laith J: Tidak banyak orang yang membaca standar ISO C ++ 786 halaman dari sampul ke sampul - tapi saya kira Anda sudah, dan Anda telah mempertahankan semuanya, bukan?
j_random_hacker

2
@ Laith, @j_random: Lihat pertanyaan saya "Apa lelucon programmer, bagaimana cara mengenalinya, dan apa tanggapan yang sesuai" di stackoverflow.com/questions/1/you-have-been-link-rolled .

Jawaban:


308

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


11
Sangat menarik. Saya dapat melihat bahwa membuat beberapa kode yang tidak dapat dibaca.
Jason Baker

112
Astaga. (a == 0? a: b) = (y <0? 10: 20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
Tak tahu apakah itu GCC spesifik, tapi saya terkejut menemukan ini juga bekerja: (value ? function1 : function2)().
Chris Burt-Brown

3
@ Chris Burt-Brown: Tidak, itu akan bekerja di mana saja jika mereka memiliki tipe yang sama (yaitu tidak ada argumen default) function1dan function2secara implisit dikonversi ke pointer fungsi, dan hasilnya secara implisit diubah kembali.
MSalters

238

Anda dapat menempatkan URI ke dalam sumber C ++ tanpa kesalahan. Sebagai contoh:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Tapi hanya satu per fungsi, saya kira? :)
Constantin

51
@jpoh: http diikuti dengan titik dua menjadi "label" yang nantinya Anda gunakan dalam pernyataan goto. Anda mendapatkan peringatan itu dari kompilator karena tidak digunakan dalam pernyataan goto apa pun pada contoh di atas.
utku_karatas

9
Anda dapat menambahkan lebih dari satu asalkan memiliki protokol yang berbeda! ftp.microsoft.com gopher: //aerv.nl/1 dan seterusnya ...
Daniel Earwicker

4
@Pavel: Pengidentifikasi diikuti oleh titik dua adalah label (untuk digunakan dengan goto, yang memang dimiliki C ++). Apa pun setelah dua garis miring adalah sebuah komentar. Oleh karena itu, dengan http://stackoverflow.com, httpadalah label (Anda secara teoritis dapat menulis goto http;), dan //stackoverflow.comhanya komentar akhir baris. Keduanya adalah C ++ legal, sehingga konstruk dikompilasi. Itu tidak melakukan sesuatu yang samar-samar berguna, tentu saja.
David Thornley

8
Sayangnya goto http;sebenarnya tidak mengikuti URL. :(
Yakov Galka

140

Aritmatika penunjuk.

Pemrogram C ++ lebih suka menghindari petunjuk karena bug yang dapat diperkenalkan.

C ++ paling keren yang pernah saya lihat? Literal analog.


11
Kami menghindari petunjuk karena bug? Pointer pada dasarnya adalah segala sesuatu tentang pengkodean C ++ dinamis!
Nick Bedford

1
Literal analog bagus untuk entri kontes C ++ yang dikaburkan, terutama jenis seni ASCII.
Synetech

119

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 )


+1 jawaban yang sangat teliti. ini tidak lengkap karena alasan yang jelas (jika tidak, tidak akan ada "fitur tersembunyi" lagi!): p di poin pertama di akhir jawaban, Anda menyebutkan anggota antarmuka kelas. maksud Anda ".. apakah fungsi-anggota dan fungsi teman non-anggota"?
wilhelmtell


apa yang Anda sebutkan dengan 1 harus koenig lookup, kan?
Özgür

1
@wilhelmtell: Tidak, tidak, tidak ... :-p ... Maksud saya "fungsi-anggota dan fungsi non-anggota NON-TEMAN" .... Pencarian Koenig akan memastikan fungsi ini akan dipertimbangkan lebih cepat daripada yang lain " luar "berfungsi untuk mencari simbol
paercebal

7
Pos yang bagus, dan +1 terutama untuk bagian terakhir, yang terlalu sedikit orang sadari. Saya mungkin akan menambahkan pustaka Boost sebagai "fitur tersembunyi" juga. Saya cukup banyak menganggapnya sebagai pustaka standar yang seharusnya dimiliki C ++. ;)
jalf

118

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

1
Saya rasa ini berguna jika Anda tidak ingin menggunakannya using.
Siqi Lin

4
Ini juga berguna sebagai cara untuk beralih antar implementasi, apakah memilih katakan aman utas versus non-utas-aman, atau versi 1 versus 2.
Tony Delroy

3
Ini sangat berguna jika Anda mengerjakan proyek yang sangat besar dengan hierarki namespace yang besar dan Anda tidak ingin header Anda menyebabkan polusi namespace (dan Anda ingin deklarasi variabel Anda dapat dibaca manusia).
Brandon Bohrer

102

Variabel tidak hanya dapat dideklarasikan di bagian init dari sebuah forloop, tetapi juga kelas dan fungsi.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Itu memungkinkan banyak variabel dengan jenis yang berbeda.


31
Senang mengetahui bahwa Anda dapat melakukannya, tetapi secara pribadi saya benar-benar berusaha menghindari melakukan hal seperti itu. Terutama karena sulit dibaca.
Zoomulator

2
Sebenarnya, yang akan berhasil dalam konteks ini adalah menggunakan pasangan: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin baik maka saya sarankan Anda untuk mencoba dan membuat laporan bug terhadap VS2008 daripada downvoting fitur tersembunyi. Ini jelas merupakan kesalahan kompiler Anda.
Johannes Schaub - litb

2
Hmm, itu tidak berfungsi di pnidui10 juga. Betapa menyedihkan :(
avakar

2
@avakar nyatanya, gcc memperkenalkan bug yang membuatnya menolak juga, di v4.6 :) lihat gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb

77

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


15
Sebenarnya saat menggunakan trik ini, Anda harus benar-benar memperhatikan jenis apa yang Anda gunakan. A [8] sebenarnya adalah ke-8 A sedangkan 8 [A] adalah integer Ath yang dimulai dari alamat 8. Jika A adalah byte, Anda memiliki bug.
Vincent Robert

38
maksud Anda "komutatif" di mana Anda mengatakan "asosiatif"?
DarenW

28
Vincent, kamu salah. Jenis Atidak penting sama sekali. Misalnya, jika Aa char*, kode tersebut akan tetap valid.
Konrad Rudolph

11
Berhati-hatilah bahwa A harus menjadi pointer, dan bukan operator overloading kelas [].
David Rodríguez - dribeas

15
Vincent, dalam hal ini harus ada satu tipe integral dan satu tipe pointer, dan C maupun C ++ tidak peduli mana yang lebih dulu.
David Thornley

73

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


Menarik! Jadi, haruskah Anda menginisialisasi semua anggota? Apakah itu mengikuti urutan struct biasa, menyiratkan bahwa anggota terakhir akan diinisialisasi "di atas" anggota sebelumnya?
j_random_hacker

j_random_hacker oh, benar itu tidak masuk akal. tangkapan yang bagus. saya menulisnya karena itu akan menjadi sebuah struct. tunggu saya akan memperbaikinya
Johannes Schaub - litb

Bukankah ini memicu perilaku tidak terdefinisi?
Greg Bacon

7
@gbacon, yes itu memanggil perilaku tak terdefinisi jika Fromdan Todisetel serta digunakan dengan semestinya . Gabungan semacam itu dapat digunakan dengan perilaku yang ditentukan (dengan Tomenjadi 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.
Johannes Schaub - litb

3
Hati-hati dengan konstruktor. Perhatikan bahwa Anda hanya diminta untuk membuat elemen pertama, dan itu hanya diperbolehkan di C ++ 0x. Pada standar saat ini, Anda harus tetap berpegang pada tipe yang dapat dibangun secara sepele. Dan tidak ada perusak.
Potatoswatter

72

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.


65

Fitur tersembunyi lainnya yang tidak berfungsi di C adalah fungsionalitas unary + operator . Anda dapat menggunakannya untuk mempromosikan dan merusak segala macam hal

Mengonversi Enumerasi menjadi integer

+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.

Dapatkan nilai dari variabel

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

Merusak array menjadi pointer

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
}

61

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

3
Bisakah Anda menjelaskannya? Seperti yang Anda hanya menggoda;)
Joseph Garvin

8
ScopeGuard ( ddj.com/cpp/184403758 ) adalah contoh bagus yang memanfaatkan fitur ini.
MSN

2
Saya bersama Joseph Garvin. Tolong beri kami pencerahan.
Peter Mortensen

Saya baru saja melakukannya di komentar. Selain itu, ini adalah konsekuensi alami dari penggunaan parameter referensi const.
MSN


52

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.


Saya rasa Anda tidak dapat returnmenangkap blok dari Coba Fungsi, hanya lempar ulang.
Constantin

Saya baru saja mencoba menyusun di atas, dan tidak ada peringatan. Saya pikir contoh di atas berhasil.
vividos

7
kembali hanya dilarang untuk konstruktor. Blok percobaan fungsi konstruktor akan menangkap kesalahan yang menginisialisasi basis dan anggota (satu-satunya kasus di mana blok percobaan fungsi melakukan sesuatu yang berbeda dari hanya mencoba di dalam fungsi); tidak melempar ulang akan menghasilkan objek yang tidak lengkap.
puetzk

Iya. Ini sangat berguna. Saya menulis makro BEGIN_COM_METHOD dan END_COM_METHOD untuk menangkap pengecualian dan mengembalikan HRESULTS sehingga pengecualian tidak bocor keluar dari kelas yang menerapkan antarmuka COM. Ini bekerja dengan baik.
Scott Langham

3
Seperti yang ditunjukkan oleh @puetzk, ini adalah satu-satunya cara untuk menangani pengecualian yang dilemparkan oleh apa pun dalam daftar penginisialisasi konstruktor , seperti konstruktor kelas dasar atau anggota data.
anton.burger

44

Banyak yang tahu tentang identity/ idmetafunction, 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; };

Menarik, tetapi awalnya saya sebenarnya lebih kesulitan membaca beberapa definisi tersebut. Cara lain untuk memperbaiki masalah inside-out dengan deklarasi C ++ adalah dengan menulis beberapa alias jenis template: 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;
bames53

42

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


Rapi! Saya tidak pernah tahu Anda bisa melakukan itu ... itu akan (dan akan) menghemat kerumitan saat menulis kode dengan nilai pengembalian kesalahan. Apakah ada cara untuk tetap memiliki kondisional daripada hanya! = 0 dalam formulir ini? if ((int r = func ()) <0) sepertinya tidak berfungsi ...
puetzk

puetzk, tidak, tidak ada. tapi senang Anda menyukainya :)
Johannes Schaub - litb

4
@ Frerich, ini tidak mungkin dalam kode C sama sekali. Saya pikir Anda sedang memikirkan if((a = f()) == b) ..., tetapi jawaban ini sebenarnya menyatakan variabel dalam kondisi tersebut.
Johannes Schaub - litb

1
@Angry sangat berbeda, karena deklarasi variabel diuji langsung untuk nilai booleannya. Ada juga pemetaan ke for-loop, yang terlihat seperti for(...; int i = foo(); ) ...;This akan melewati body selama itrue, menginisialisasinya setiap kali lagi. Perulangan yang Anda perlihatkan hanya mendemonstrasikan deklarasi variabel, tetapi bukan deklarasi variabel yang secara bersamaan bertindak sebagai kondisi :)
Johannes Schaub - litb

5
Sangat bagus, kecuali Anda tidak menyebutkan tujuan penggunaan fitur ini adalah untuk cast penunjuk dinamis, saya yakin.
mmocny

29

Mencegah operator koma memanggil operator kelebihan beban

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.


Saya hanya menggunakan ini untuk menyelesaikan pengacau ekspresi saya yang berlebihan . :)
GManNickG

28

Inisialisasi array dalam konstruktor. Misalnya di kelas jika kita memiliki array intsebagai:

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()
{
}

6
Anda dapat melakukan ini dengan larik apa pun di mana saja.
Potatoswatter

@Potatoswatter: lebih sulit dari yang terlihat, karena penguraian yang paling menjengkelkan. Saya tidak bisa memikirkan tempat lain yang bisa dilakukan, kecuali mungkin nilai
baliknya

Jika tipe dari array adalah tipe kelas, maka ini tidak diperlukan bukan?
Thomas Eding

27

Oooh, saya bisa membuat daftar kebencian hewan peliharaan sebagai gantinya:

  • Destruktor harus berbentuk virtual jika Anda bermaksud menggunakan secara polimorfis
  • Terkadang anggota diinisialisasi secara default, terkadang tidak
  • Klas lokal tidak dapat digunakan sebagai parameter template (membuatnya kurang berguna)
  • penentu pengecualian: terlihat berguna, tetapi tidak
  • kelebihan fungsi menyembunyikan fungsi kelas dasar dengan tanda tangan yang berbeda.
  • tidak ada standardisasi yang berguna untuk internasionalisasi (siapa saja yang bisa menggunakan set karakter lebar standar portabel? Kami harus menunggu hingga C ++ 0x)

Di sisi positifnya

  • fitur tersembunyi: fungsi coba blok. Sayangnya saya belum menemukan kegunaannya. Ya, saya tahu mengapa mereka menambahkannya, tetapi Anda harus memasukkannya kembali ke dalam konstruktor yang membuatnya tidak berguna.
  • Sebaiknya perhatikan dengan cermat jaminan STL tentang validitas iterator setelah modifikasi container, yang memungkinkan Anda membuat loop yang sedikit lebih bagus.
  • Boost - ini bukan rahasia tetapi layak digunakan.
  • Pengoptimalan nilai pengembalian (tidak jelas, tetapi secara khusus diizinkan oleh standar)
  • Functor alias objek fungsi alias operator (). Ini digunakan secara luas oleh STL. sebenarnya bukan rahasia, tetapi merupakan efek samping yang bagus dari kelebihan beban operator dan templat.

16
hewan peliharaan benci: tidak ada ABI yang ditentukan untuk aplikasi C ++, tidak seperti C yang digunakan semua orang karena setiap bahasa dapat menjamin untuk memanggil fungsi C, tidak ada yang dapat melakukan hal yang sama untuk C ++.
gbjbaanb

8
Destructors harus virtual hanya jika Anda ingin menghancurkan secara polimorfis, yang sedikit berbeda dari poin pertama.
David Rodríguez - dribeas

2
Dengan C ++ 0x tipe lokal dapat digunakan sebagai parameter template.
tstenner

1
Dengan C ++ 0x, destruktor akan menjadi virtual jika objek memiliki fungsi virtual (yaitu vtable).
Macke

jangan lupa NRVO, dan tentu saja optimasi apapun diperbolehkan selama tidak mengubah keluaran program
jk.

26

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 Cobjek, di mana kelas Btidak memiliki bisnis dan petunjuk tentang datanya. Akses tersebut hanya diberikan jika xmerupakan 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::stackcontoh.

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


26

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


1
Ketika Anda mengatakan resolusi kelebihan beban dilakukan pada hasil mereka, apakah yang Anda maksud itu benar-benar mengubahnya menjadi kedua Functor dan kemudian melakukan resolusi berlebihan? Saya mencoba mencetak sesuatu di operator Func1 * (), dan operator Func2 * (), tetapi tampaknya memilih yang benar ketika mengetahui operator konversi mana yang akan dipanggil.
navigator

3
@navigator, ya secara konseptual mengonversi keduanya dan kemudian memilih yang terbaik. Ia tidak perlu benar-benar memanggil mereka, karena ia tahu dari jenis hasil apa yang akan mereka hasilkan. Panggilan sebenarnya dilakukan ketika ternyata apa yang akhirnya dipilih.
Johannes Schaub - litb

26

Fitur tersembunyi:

  1. Fungsi virtual murni dapat diimplementasikan. Contoh umum, penghancur virtual murni.
  2. Jika suatu fungsi menampilkan pengecualian yang tidak tercantum dalam spesifikasi pengecualiannya, tetapi fungsi tersebut memiliki std::bad_exceptionspesifikasi pengecualiannya, pengecualian tersebut diubah menjadi std::bad_exceptiondan ditampilkan secara otomatis. Dengan cara itu Anda setidaknya akan tahu bahwa ada bad_exceptionyang terlempar. Baca lebih lanjut di sini .

  3. fungsi coba blok

  4. 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 .

  5. default parameter fungsi dapat diubah saat runtime. Baca lebih lanjut di sini .

  6. A[i] bekerja sebaik i[A]

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

  8. 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)
    }

P Ayah: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Saya mendapatkan pergantian, hanya saja ini berarti bahwa [] tidak memiliki nilai semantiknya sendiri dan hanya setara dengan pengganti gaya makro di mana "x [y]" diganti dengan "(* ((x) + (y ))) ". Sama sekali tidak seperti yang saya harapkan. Saya bertanya-tanya mengapa itu didefinisikan seperti ini.
P Daddy

Kompatibilitas mundur dengan C
jmucchiello

2
Mengenai poin pertama Anda: Ada satu kasus khusus di mana Anda harus menerapkan fungsi virtual murni: penghancur virtual murni.
Frerich Raabe

24

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.


11
Dan di sisi lain Anda tidak dapat menggunakan operator [] pada peta const
David Rodríguez - dribeas

2
+1 untuk Nick, orang bisa jadi gila jika mereka tidak mengetahuinya .find().
LiraNuna

atau " const map::operator[]menghasilkan pesan kesalahan"
hanya seseorang

2
Bukan fitur bahasa, ini adalah fitur pustaka Templat standar. Ini juga cukup jelas, karena operator [] mengembalikan referensi yang valid.
Ramon Zarazua B.

2
Saya harus menggunakan peta di C # untuk sementara waktu, di mana peta tidak berperilaku seperti itu, untuk menyadari bahwa ini adalah fitur. Saya pikir saya lebih terganggu olehnya daripada saya menggunakannya, tetapi sepertinya saya salah. Saya melewatkannya di C #.
sbi

20

Menempatkan fungsi atau variabel dalam namespace tanpa nama akan menghentikan penggunaan staticuntuk membatasinya ke cakupan file.


"deprecates" adalah istilah yang kuat…
Potatoswatter

@ Potato: Komentar lama, saya tahu, tetapi standar mengatakan penggunaan statis dalam lingkup namespace tidak digunakan lagi, dengan preferensi untuk namespace yang tidak disebutkan namanya.
GManNickG

@GMan: tidak masalah, saya rasa halaman SO tidak benar-benar "mati". Hanya untuk kedua sisi cerita, staticdalam lingkup global sama sekali tidak ditinggalkan. (Untuk referensi: C ++ 03 §D.2)
Potatoswatter

Ah, setelah membaca lebih dekat, "Sebuah nama yang dideklarasikan dalam namespace global memiliki cakupan namespace global (juga disebut cakupan global)." Apakah itu benar-benar berarti?
Potatoswatter

@ Kentang: Yup. :) staticpenggunaan sebaiknya hanya digunakan di dalam tipe kelas atau fungsi.
GManNickG

19

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


18

fungsi void dapat mengembalikan nilai void

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: voidfungsi 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; !
};

17

Membaca file menjadi vektor string:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Atau: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
Anda maksud vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- kurung hilang setelah param kedua
knittl

1
Ini sebenarnya bukan fitur C ++ tersembunyi. Lebih dari fitur STL. STL! = Bahasa
Nick Bedford

14

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.


1
Lihat di sini, di mana saya baru-baru ini menyarankannya untuk aritmatika n-bit: stackoverflow.com/questions/8309538/…
lihat

14

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 SomeTypeobjek pada tumpukan dan menginisialisasinya (dengan udalam 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 SomeTypebernama u.


apakah ada perbedaan antara 1 dan 2? meskipun, saya tahu keduanya adalah inisialisasi.
Özgür

Comptrol: Saya kira tidak. Keduanya akan berakhir dengan memanggil konstruktor salinan, meskipun yang pertama TERLIHAT seperti operator penugasan, itu sebenarnya adalah konstruktor salinan.
abelenky

1
Jika u adalah tipe yang berbeda dari SomeType, maka yang pertama akan memanggil konstruktor konversi terlebih dahulu dan kemudian menyalin konstruktor, sedangkan yang kedua hanya akan memanggil konstruktor konversi.
Gerhana

3
Pertama adalah panggilan implisit dari konstruktor, kedua adalah panggilan eksplisit. Lihatlah kode berikut untuk melihat perbedaannya: #include <iostream> class sss {public: eksplisit sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // memanggil int konstruktor sss xxx = 7; // memanggil konstruktor ganda return 0; }
Kirill V. Lyadvinsky

Benar - baris pertama tidak akan berfungsi jika konstruktor dinyatakan eksplisit.
Gerhana

12

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;

2
FStruct s = {};bahkan lebih pendek.
Constantin

Pada contoh terakhir, akan lebih sederhana dengan: a (); b (); mengapung c = 1.0f;
Zifre

2
Sintaks ini "float c = (a (), b (), 1.0f);" berguna untuk memberi penekanan pada operasi penugasan (penetapan "c"). Operasi penugasan penting dalam pemrograman karena mereka cenderung menjadi IMO yang tidak digunakan lagi. Tidak tahu mengapa, mungkin ada hubungannya dengan pemrograman fungsional di mana status program diberikan kembali setiap frame. PS. Dan tidak, "int d = (11,22,1.0f)" akan sama dengan "1". Diuji satu menit yang lalu dengan VS2008.
AareP

2
+1 Bukankah Anda harus menelepon main ? Saya sarankan global().main();dan lupakan saja tentang singleton ( Anda bisa bekerja dengan yang sementara, yang diperpanjang seumur hidup )
lihat

1
Saya ragu menugaskan referensi itu portabel. Saya suka struct untuk mengesampingkan deklarasi ke depan.
Thomas Eding

12

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


Untuk apa nilainya, Neil memiliki pertanyaan tentang ini: stackoverflow.com/questions/1212978/… , hanya untuk info tambahan.
GManNickG

12

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.


1
Rapi. Adakah alasan khusus Anda memasukkan struct Cdalam contoh Anda ...? Bersulang.
Tony Delroy
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.