มีหลายวิธีในการเขียน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) เพื่อให้สามารถพบได้