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 suatu swapfungsi.
Kami pertama kali melihat bahwa wadah seperti std::vector<>memiliki fungsi anggota argumen tunggal swap, seperti:
struct vector
{
void swap(vector&) { /* swap members */ }
};
Jadi, tentu saja, kelas kita juga seharusnya, kan? Yah, tidak juga. Perpustakaan standar memiliki segala macam hal yang tidak perlu , dan anggota swapadalah 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 perilaku std::swap.
Kalau begitu, untuk membuat std::swappekerjaan kita harus menyediakan (dan std::vector<>seharusnya menyediakan) spesialisasi std::swap, kan?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
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:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
Metode ini berfungsi beberapa saat, tetapi tidak selalu. Pasti ada cara yang lebih baik.
Ada! Kita dapat menggunakan suatu friendfungsi, dan menemukannya melalui ADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Ketika kami ingin menukar sesuatu, kami kaitkan † std::swap dan kemudian membuat panggilan yang tidak memenuhi syarat:
using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first
// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap
Apa itu friendfungsi? Ada kebingungan di sekitar area ini.
Sebelum C ++ distandarisasi, friendfungsi 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:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
Namun, ketika ADL ditemukan ini dihapus. The friendFungsi 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 "lihat std, 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 menangani std::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 kualifikasi swap, 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 pengecualian swap, dengan std::swapsebagai 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 friendfungsinya lengkap dan berfungsi. Dan saat Anda bertukar, gunakan boost::swapatau tidak berkualifikasi swapdengan std::swapterkait.
† Secara informal, nama dikaitkan jika akan dipertimbangkan selama panggilan fungsi. Untuk detailnya, baca §3.4.2. Dalam hal ini, std::swapbiasanya tidak dipertimbangkan; tetapi kami dapat mengaitkannya (menambahkannya ke kumpulan kelebihan yang dianggap tidak memenuhi syarat swap), memungkinkannya ditemukan.