Dalam jawaban yang indah untuk idiom copy-and-swap ada sepotong kode saya butuh bantuan:
class dumb_array
{
public:
// ...
friend void swap(dumb_array& first, dumb_array& second) // nothrow
{
using std::swap;
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
// ...
};
dan dia menambahkan catatan
Ada klaim lain bahwa kita harus mengkhususkan std :: swap untuk tipe kita, menyediakan swap di kelas di samping swap fungsi-bebas, dll. Tapi ini semua tidak perlu: setiap penggunaan swap yang tepat akan melalui panggilan yang tidak memenuhi syarat , dan fungsi kami akan ditemukan melalui ADL. Satu fungsi akan dilakukan.
Dengan friend
saya sedikit pada istilah "tidak ramah", saya harus mengakui. Jadi, pertanyaan utama saya adalah:
- terlihat seperti fungsi bebas , tetapi di dalam tubuh kelas?
- mengapa ini tidak
swap
statis ? Itu jelas tidak menggunakan variabel anggota. - "Adakah penggunaan swap yang tepat akan menemukan swap melalui ADL" ? ADL akan mencari ruang nama, kan? Tetapi apakah itu juga terlihat di dalam kelas? Atau di sinilah tempatnya
friend
?
Pertanyaan sampingan:
- Dengan C ++ 11, harus saya menandai saya
swap
s dengannoexcept
? - Dengan C ++ 11 dan range-for-nya , haruskah saya menempatkan
friend iter begin()
danfriend iter end()
dengan cara yang sama di dalam kelas? Saya pikirfriend
tidak diperlukan di sini, kan?
c++
c++11
friend
copy-and-swap
towi
sumber
sumber
friend
fungsi sama sekali bukan fungsi anggota.Jawaban:
Ada beberapa cara untuk menulis
swap
, beberapa lebih baik daripada yang lain. Namun seiring waktu, ditemukan satu definisi yang paling baik. Mari kita pikirkan bagaimana kita berpikir tentang menulis suatuswap
fungsi.Kami pertama kali melihat bahwa wadah seperti
std::vector<>
memiliki fungsi anggota argumen tunggalswap
, seperti:Jadi, tentu saja, kelas kita juga seharusnya, kan? Yah, tidak juga. Perpustakaan standar memiliki segala macam hal yang tidak perlu , dan anggota
swap
adalah salah satunya. Mengapa? Ayo pergi.Yang harus kita lakukan adalah mengidentifikasi apa yang kanonik, dan apa yang perlu dilakukan kelas kita untuk mengatasinya. Dan metode kanonik swapping adalah dengan
std::swap
. Inilah sebabnya mengapa fungsi anggota tidak berguna: mereka bukan bagaimana kita harus bertukar sesuatu, secara umum, dan tidak berpengaruh pada perilakustd::swap
.Kalau begitu, untuk membuat
std::swap
pekerjaan kita harus menyediakan (danstd::vector<>
seharusnya menyediakan) spesialisasistd::swap
, kan?Yah itu pasti akan berhasil dalam kasus ini, tetapi ia memiliki masalah mencolok: fungsi spesialisasi tidak dapat parsial. Artinya, kami tidak dapat mengkhususkan kelas template dengan ini, hanya instantiations tertentu:
Metode ini berfungsi beberapa saat, tetapi tidak selalu. Pasti ada cara yang lebih baik.
Ada! Kita dapat menggunakan suatu
friend
fungsi, dan menemukannya melalui ADL :Ketika kami ingin menukar sesuatu, kami kaitkan †
std::swap
dan kemudian membuat panggilan yang tidak memenuhi syarat:Apa itu
friend
fungsi? Ada kebingungan di sekitar area ini.Sebelum C ++ distandarisasi,
friend
fungsi melakukan sesuatu yang disebut "injeksi nama teman", di mana kode berperilaku seolah-olah fungsi tersebut telah ditulis dalam namespace sekitarnya. Misalnya, ini adalah pra-standar yang setara:Namun, ketika ADL ditemukan ini dihapus. The
friend
Fungsi bisa kemudian hanya ditemukan melalui ADL; jika Anda menginginkannya sebagai fungsi bebas, itu perlu dinyatakan demikian ( lihat ini , misalnya). Tapi lihat! Ada masalah.Jika Anda hanya menggunakan
std::swap(x, y)
, kelebihan Anda tidak akan pernah ditemukan, karena Anda telah secara eksplisit mengatakan "lihatstd
, dan tempat lain"! Inilah sebabnya mengapa beberapa orang menyarankan untuk menulis dua fungsi: satu sebagai fungsi yang dapat ditemukan melalui ADL , dan yang lainnya untuk menanganistd::
kualifikasi eksplisit .Tapi seperti yang kita lihat, ini tidak bisa bekerja di semua kasus, dan kita berakhir dengan kekacauan yang jelek. Alih-alih, bertukar idiomatik memilih jalan lain: alih-alih menjadikannya tugas kelas untuk menyediakan
std::swap
, itu adalah tugas para penukar untuk memastikan mereka tidak menggunakan kualifikasiswap
, seperti di atas. Dan ini cenderung bekerja dengan baik, selama orang tahu tentang itu. Tapi di situlah masalahnya: tidak perlu untuk menggunakan panggilan yang tidak memenuhi syarat!Untuk membuat ini lebih mudah, beberapa perpustakaan seperti Boost menyediakan fungsi
boost::swap
, yang hanya melakukan panggilan wajar tanpa pengecualianswap
, denganstd::swap
sebagai namespace terkait. Ini membantu membuat hal-hal ringkas lagi, tetapi masih mengecewakan.Perhatikan bahwa tidak ada perubahan dalam C ++ 11 untuk perilaku
std::swap
, yang saya dan orang lain salah sangka akan menjadi kasusnya. Jika Anda kesal dengan ini, baca di sini .Singkatnya: fungsi anggota hanyalah noise, spesialisasi jelek dan tidak lengkap, tetapi
friend
fungsinya lengkap dan berfungsi. Dan saat Anda bertukar, gunakanboost::swap
atau tidak berkualifikasiswap
denganstd::swap
terkait.† Secara informal, nama dikaitkan jika akan dipertimbangkan selama panggilan fungsi. Untuk detailnya, baca §3.4.2. Dalam hal ini,
std::swap
biasanya tidak dipertimbangkan; tetapi kami dapat mengaitkannya (menambahkannya ke kumpulan kelebihan yang dianggap tidak memenuhi syaratswap
), memungkinkannya ditemukan.sumber
std::vector<std::string>().swap(someVecWithData);
, yang tidak mungkin denganswap
fungsi bebas karena kedua argumen dilewatkan oleh referensi non-const.operator=
,operator+
danoperator+=
, tapi jelas mereka operator di kelas yang relevan diterima / diharapkan ada untuk simetri. Hal yang sama berlaku untuk anggotaswap
+ namespace-lingkupswap
dalam pendapat saya.function<void(A*)> f; if(!f) { }
dapat gagal hanya karenaA
menyatakanoperator!
bahwa yang menerimaf
sama baiknya denganf
miliknya sendirioperator!
(tidak mungkin, tetapi dapat terjadi). Jikafunction<>
penulis berpikir "ohh saya punya 'operator bool', mengapa saya harus menerapkan 'operator!'? Itu akan melanggar KERING!", Itu akan berakibat fatal. Anda hanya perluoperator!
menerapkan untukA
, danA
memiliki konstruktor untukfunction<...>
, dan segala sesuatunya akan rusak, karena kedua kandidat akan memerlukan konversi yang ditentukan pengguna.Kode itu setara ( hampir di setiap cara) untuk:
Fungsi teman yang didefinisikan di dalam kelas adalah:
inline
Aturan persisnya ada di bagian
[class.friend]
(saya kutip paragraf 6 dan 7 dari konsep C ++ 0x):sumber
swap
hanya terlihat oleh ADL. Itu adalah anggota namespace yang melampirkan, tetapi namanya tidak terlihat oleh formulir pencarian nama lainnya. EDIT: Saya melihat bahwa @ GM dapat lebih cepat lagi :) @ Kalau selalu seperti itu di ISO C ++ :)friend
fungsi hanya ditemukan oleh ADL, dan jika mereka hanya perlu fungsi bebas denganfriend
akses, keduanya harus dinyatakan sebagaifriend
di dalam kelas, dan sebagai deklarasi fungsi bebas normal di luar kelas. Anda dapat melihat keharusan itu dalam jawaban ini , misalnya.