มีหลายวิธีในการเขียนswap
ดีกว่าวิธีอื่น เมื่อเวลาผ่านไปก็พบว่าคำนิยามเดียวทำงานได้ดีที่สุด ลองพิจารณาวิธีที่เราอาจคิดเกี่ยวกับการเขียนswap
ฟังก์ชั่น
ก่อนอื่นเราจะเห็นว่าคอนเทนเนอร์เช่นstd::vector<>
มีฟังก์ชั่นสมาชิกอาร์กิวเมนต์เดียวswap
เช่น:
struct vector
{
void swap(vector&) { /* swap members */ }
};
โดยปกติแล้วชั้นเรียนของเราก็ควรเช่นกันใช่ไหม? ก็ไม่ได้จริงๆ ไลบรารีมาตรฐานมีสิ่งที่ไม่จำเป็นทุกประเภทและสมาชิกswap
เป็นหนึ่งในนั้น ทำไม? ไปกันเถอะ
สิ่งที่เราควรทำคือการระบุสิ่งที่เป็นที่ยอมรับและสิ่งที่ชั้นเรียนของเราต้องทำเพื่อทำงานกับมัน std::swap
และวิธีการที่ยอมรับของการแลกเปลี่ยนอยู่กับ นี่คือเหตุผลที่ฟังก์ชั่นสมาชิกไม่ได้มีประโยชน์: std::swap
พวกเขาไม่ได้ว่าเราควรสลับสิ่งที่โดยทั่วไปและไม่มีผลต่อการทำงานของ
ถ้าอย่างนั้นในการstd::swap
ทำงานเราควรจัดให้std::vector<>
มีความเชี่ยวชาญเป็นพิเศษstd::swap
ใช่ไหม?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
แน่นอนว่ามันจะใช้งานได้ในกรณีนี้ แต่มันมีปัญหาที่เห็นได้ชัด: ความสามารถเฉพาะด้านของฟังก์ชันไม่สามารถเป็นส่วนหนึ่งได้ นั่นคือเราไม่สามารถเชี่ยวชาญคลาสเทมเพลตด้วยสิ่งนี้มีเพียงอินสแตนซ์เฉพาะ
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
วิธีนี้ใช้งานได้ในบางเวลา แต่ไม่ตลอดเวลา จะต้องมีวิธีที่ดีกว่า
มี! เราสามารถใช้friend
ฟังก์ชั่นและค้นหาผ่านADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
เมื่อเราต้องการแลกเปลี่ยนบางสิ่งเราเชื่อมโยง† std::swap
แล้วทำการโทรอย่างไม่มีเงื่อนไข:
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
ก. คืออะไร friend
ฟังก์ชั่น? มีความสับสนบริเวณนี้
ก่อนที่ C ++ จะได้มาตรฐานfriend
ฟังก์ชั่นทำอะไรบางอย่างที่เรียกว่า "การฉีดชื่อเพื่อน" โดยที่โค้ดนั้นทำตัวราวกับว่าฟังก์ชั่นนั้นถูกเขียนในเนมสเปซรอบ ตัวอย่างเช่นสิ่งเหล่านี้เทียบเท่ามาตรฐานล่วงหน้า:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
อย่างไรก็ตามเมื่อADLถูกประดิษฐ์สิ่งนี้จะถูกลบ friend
ฟังก์ชั่นสามารถแล้วเท่านั้นที่จะพบผ่าน ADL; ถ้าคุณต้องการให้มันเป็นฟังก์ชั่นฟรีมันจำเป็นต้องได้รับการประกาศให้เป็นเช่นนั้น ( ดูสิ่งนี้ตัวอย่างนี้) แต่แท้จริง! มีปัญหา.
หากคุณเพิ่งใช้งานstd::swap(x, y)
เกินพิกัดของคุณจะไม่ถูกค้นพบเพราะคุณได้พูดอย่างชัดเจนว่า "มองเข้าไปstd
และไม่มีที่อื่นอีกแล้ว"! นี่คือสาเหตุที่บางคนแนะนำให้เขียนสองฟังก์ชัน: อันหนึ่งเป็นฟังก์ชั่นที่จะพบได้ผ่านADLและอีกอันเพื่อจัดการstd::
คุณสมบัติที่ชัดเจน
แต่อย่างที่เราเห็นมันไม่สามารถทำงานได้ในทุกกรณีและเราจบลงด้วยความยุ่งเหยิงน่าเกลียด แต่การแลกเปลี่ยนเป็นไปในทิศทางอื่น: แทนที่จะทำให้มันเป็นงานของชั้นเรียนที่จะให้std::swap
มันเป็นงานของนักเปลี่ยนเครื่องเพื่อให้แน่ใจว่าพวกเขาจะไม่ใช้คุณสมบัติที่เหมาะสมswap
กล่าวไว้ข้างต้น และสิ่งนี้มีแนวโน้มที่จะทำงานได้ดีตราบใดที่ผู้คนรู้เรื่องนี้ แต่ปัญหานั้นอยู่ที่: มันไม่ง่ายเลยที่จะต้องใช้สายที่ไม่มีเงื่อนไข!
เพื่อให้ง่ายขึ้นห้องสมุดบางแห่งเช่น Boost ได้จัดให้มีฟังก์ชั่นboost::swap
ซึ่งเป็นการเรียกที่ไม่เหมาะสมswap
โดยใช้std::swap
เป็นเนมสเปซที่เกี่ยวข้อง สิ่งนี้ช่วยทำให้รวบรัดอีกครั้ง แต่ก็ยังเป็นคนเกียจคร้าน
โปรดทราบว่าไม่มีการเปลี่ยนแปลงใน C ++ 11 กับพฤติกรรมของstd::swap
ซึ่งฉันและคนอื่น ๆ คิดว่าผิดจะเป็นกรณี ถ้าคุณเป็น bit โดยนี้อ่านได้ที่นี่
ในระยะสั้น: ฟังก์ชั่นสมาชิกเป็นเพียงเสียงความเชี่ยวชาญเป็นที่น่าเกลียดและไม่สมบูรณ์ แต่friend
ฟังก์ชั่นจะเสร็จสมบูรณ์และใช้งานได้ และเมื่อคุณแลกเปลี่ยนไม่ว่าจะใช้boost::swap
หรือไม่มีเงื่อนไขswap
กับการstd::swap
เชื่อมโยง
†อย่างไม่เป็นทางการชื่อจะถูกเชื่อมโยงถ้ามันจะถูกพิจารณาในระหว่างการเรียกใช้ฟังก์ชั่น สำหรับรายละเอียดอ่าน§3.4.2 ในกรณีนี้std::swap
ปกติไม่ได้รับการพิจารณา แต่เราสามารถเชื่อมโยงมัน (เพิ่มลงในชุดของการโอเวอร์โหลดที่พิจารณาโดยไม่มีเงื่อนไขswap
) เพื่อให้สามารถพบได้