ฟังก์ชั่นการแลกเปลี่ยนเพื่อนสาธารณะ


169

ในคำตอบที่สวยงามของcopy-and-swap-idiomมีโค้ดบางส่วนที่ฉันต้องการความช่วยเหลือเล็กน้อย:

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

และเขาเพิ่มบันทึก

มีการอ้างสิทธิ์อื่น ๆ ที่เราควรชำนาญ std :: swap สำหรับประเภทของเราให้การแลกเปลี่ยนในชั้นเรียนพร้อมการแลกเปลี่ยนฟังก์ชั่นฟรีเป็นต้น แต่สิ่งนี้ไม่จำเป็นทั้งหมด: การใช้ swap ที่เหมาะสมจะต้องผ่านการโทรที่ไม่มีเงื่อนไข และฟังก์ชั่นของเราจะพบผ่าน ADL ฟังก์ชั่นหนึ่งจะทำ

เนื่องจากfriendฉันใช้คำศัพท์ที่ "ไม่เป็นมิตร" ฉันต้องยอมรับ ดังนั้นคำถามหลักของฉันคือ:

  • ดูเหมือนว่าฟังก์ชั่นฟรีแต่ภายในร่างกายของคลาส?
  • ไม่ได้เป็นเหตุผลนี้swapคง ? เห็นได้ชัดว่ามันไม่ได้ใช้ตัวแปรสมาชิกใด ๆ
  • "ห้ามการใช้งานที่เหมาะสมของการแลกเปลี่ยนจะพบแลกเปลี่ยนผ่าน ADL" ? ADL จะค้นหาเนมสเปซใช่ไหม แต่มันก็ดูในชั้นเรียนด้วยหรือไม่ หรืออยู่ที่นี่ที่ไหนfriendมา?

ด้านข้างคำถาม:

  • ด้วย C ++ 11 ฉันควรทำเครื่องหมายswapด้วยnoexceptหรือไม่
  • ด้วย C ++ 11 และช่วงสำหรับฉันควรวางfriend iter begin()และfriend iter end()วิธีเดียวกันในชั้นเรียนหรือไม่ ฉันคิดว่าfriendไม่จำเป็นที่นี่ใช่ไหม

พิจารณาคำถามข้างเคียงเกี่ยวกับช่วงอิง: มันเป็นความคิดที่ดีกว่าที่จะเขียนฟังก์ชั่นสมาชิกและออกจากการเข้าถึงช่วงที่เริ่มต้น () และสิ้นสุด () ใน std namespace (§24.6.5) ตามช่วงสำหรับใช้ภายในเหล่านี้จากทั่วโลกหรือ std namespace (ดู§6.5.4) อย่างไรก็ตามมันมาพร้อมกับข้อเสียที่ฟังก์ชั่นเหล่านี้เป็นส่วนหนึ่งของส่วนหัว <iterator> หากคุณไม่ได้รวมไว้คุณอาจต้องการเขียนด้วยตนเอง
Vitus

2
ทำไมมันไม่คงที่ - เพราะfriendฟังก์ชั่นไม่ใช่ฟังก์ชั่นสมาชิกเลย
aschepler

คำตอบ:


175

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


10
ฉันไม่เห็นด้วยว่าฟังก์ชั่นสมาชิกเป็นเพียงเสียงรบกวน ฟังก์ชั่นสมาชิกช่วยให้เช่นstd::vector<std::string>().swap(someVecWithData);ซึ่งเป็นไปไม่ได้กับswapฟังก์ชั่นฟรีเพราะข้อโต้แย้งทั้งสองจะถูกส่งผ่านโดยการอ้างอิงที่ไม่ใช่ const
ildjarn

3
@ildjarn: คุณสามารถทำได้สองบรรทัด การมีฟังก์ชั่นสมาชิกละเมิดหลักการ DRY
GManNickG

4
@GMan: หลักการแบบแห้งไม่ได้ใช้หากมีการนำไปใช้ในรูปแบบอื่น มิฉะนั้นจะไม่มีใครสนับสนุนระดับที่มีการใช้งานoperator=, operator+และoperator+=แต่เห็นได้ชัดว่าผู้ประกอบการผู้ที่อยู่ในชั้นเรียนที่เกี่ยวข้องได้รับการยอมรับ / คาดว่าจะมีอยู่สำหรับสมมาตร กันไปสำหรับสมาชิกswap+ namespace- ขอบเขตswapในความคิดของฉัน
ildjarn

3
@GMan ฉันคิดว่ามันกำลังพิจารณาฟังก์ชั่นมากเกินไป ไม่ค่อยมีใครรู้จัก แต่แม้แต่ a function<void(A*)> f; if(!f) { }สามารถล้มเหลวเพียงเพราะAประกาศสิ่งoperator!ที่ยอมรับfอย่างเท่าเทียมกันในfตัวของมันเองoperator!(ไม่น่าเป็นไปได้ แต่สามารถเกิดขึ้นได้) หากfunction<>ผู้เขียนคิดว่า "โอ้ฉันมี" โอเปอเรเตอร์บูล "ทำไมฉันจึงควรใช้โอเปอเรเตอร์"! "นั่นจะเป็นการละเมิด DRY!" ซึ่งอาจเป็นอันตรายถึงชีวิต คุณเพียงแค่ต้องมีการoperator!ติดตั้งAและAมีคอนสตรัคเตอร์สำหรับ a function<...>และสิ่งต่าง ๆ จะแตกเพราะผู้สมัครทั้งสองจะต้องมีการแปลงที่ผู้ใช้กำหนด
Johannes Schaub - litb

1
ลองพิจารณาว่าเราอาจคิดอย่างไรเกี่ยวกับการเขียนฟังก์ชั่นการแลกเปลี่ยน [สมาชิก] โดยปกติแล้วชั้นเรียนของเราก็ควรจะใช่ไหม? ก็ไม่ได้จริงๆ ไลบรารีมาตรฐานมีสิ่งที่ไม่จำเป็นทุกประเภทและการแลกเปลี่ยนสมาชิกเป็นหนึ่งในนั้น GotW ที่เชื่อมโยงสนับสนุนสำหรับฟังก์ชันการแลกเปลี่ยนสมาชิก
Xeverous

7

รหัสนั้นเทียบเท่า ( เกือบทุกวิธี) เพื่อ:

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

ฟังก์ชั่นเพื่อนที่กำหนดไว้ในชั้นเรียนคือ:

  • วางในเนมสเปซที่ล้อมรอบ
  • อัตโนมัติ inline
  • สามารถอ้างถึงสมาชิกแบบคงที่ของคลาสได้โดยไม่ต้องมีคุณสมบัติเพิ่มเติม

กฎที่แน่นอนอยู่ในส่วน[class.friend](ฉันอ้างวรรค 6 และ 7 ของร่าง C ++ 0x):

ฟังก์ชั่นสามารถกำหนดได้ในการประกาศเพื่อนของชั้นเรียนถ้าหากชั้นเรียนเป็นชั้นที่ไม่ใช่ท้องถิ่น (9.8) ชื่อฟังก์ชั่นจะไม่มีเงื่อนไขและฟังก์ชั่นมีขอบเขต namespace

ฟังก์ชั่นดังกล่าวเป็นแบบอินไลน์โดยปริยาย ฟังก์ชั่นเพื่อนที่กำหนดไว้ในชั้นเรียนอยู่ในขอบเขต (ศัพท์) ของชั้นเรียนที่มันถูกกำหนด ฟังก์ชั่นเพื่อนที่กำหนดนอกชั้นเรียนไม่ได้


2
ที่จริงแล้วฟังก์ชั่นเพื่อนไม่ได้อยู่ในเนมสเปซที่ล้อมรอบในมาตรฐาน C ++ พฤติกรรมเดิมเรียกว่า "การฉีดชื่อเพื่อน" แต่ถูกแทนที่ด้วย ADL ซึ่งถูกแทนที่ด้วยมาตรฐานแรก ดูด้านบนของนี้ (พฤติกรรมนี้ค่อนข้างคล้ายกัน)
GManNickG

1
ไม่เทียบเท่ากันจริงๆ รหัสในคำถามสร้างขึ้นเพื่อให้swapสามารถมองเห็นได้เฉพาะ ADL มันเป็นสมาชิกของเนมสเปซที่ล้อมรอบ แต่ชื่อของมันจะไม่ปรากฏในแบบฟอร์มการค้นหาชื่ออื่น แก้ไข: ฉันเห็นว่า @GMan เร็วขึ้นอีกครั้ง :) @Ben มันเป็นแบบนั้นเสมอใน ISO C ++ :)
Johannes Schaub - litb

2
@Ben: ไม่การฉีดของเพื่อนไม่เคยมีมาตรฐาน แต่ใช้กันอย่างแพร่หลายมาก่อนซึ่งเป็นสาเหตุที่ความคิด (และการสนับสนุนคอมไพเลอร์) มีแนวโน้มที่จะดำเนินต่อไป แต่ก็ไม่มีเทคนิค friendฟังก์ชั่นถูกค้นพบโดย ADL เท่านั้นและหากพวกเขาต้องการเพียงแค่ฟังก์ชั่นอิสระที่มีfriendการเข้าถึงพวกเขาจำเป็นต้องประกาศทั้งสองอย่างfriendภายในคลาสและเป็นการประกาศฟังก์ชั่นฟรีตามปกตินอกชั้นเรียน คุณสามารถเห็นความจำเป็นในคำตอบนี้ยกตัวอย่างเช่น
GManNickG

2
@towi: เนื่องจากฟังก์ชั่นเพื่อนอยู่ในขอบเขตเนมสเปซคำตอบของคำถามทั้งสามของคุณควรชัดเจน: (1) มันเป็นฟังก์ชั่นฟรีรวมทั้งมีการเข้าถึงเพื่อนกับสมาชิกส่วนตัวและสมาชิกที่ได้รับการป้องกันของชั้นเรียน (2) ไม่ใช่สมาชิกเลยไม่ว่าจะเป็นกรณีหรือคงที่ (3) ADL ไม่ได้ค้นหาภายในคลาส แต่มันก็โอเคเพราะฟังก์ชั่นเพื่อนมีขอบเขตเนมสเปซ
Ben Voigt

1
@ Ben ในข้อมูลจำเพาะฟังก์ชั่นเป็นสมาชิกเนมสเปซและวลี "ฟังก์ชั่นมีขอบเขตเนมสเปซ" สามารถตีความได้เพื่อบอกว่าฟังก์ชั่นเป็นสมาชิกเนมสเปซ (มันค่อนข้างขึ้นอยู่กับบริบทของคำสั่งดังกล่าว) และจะเพิ่มชื่อในเนมสเปซนั้นที่ปรากฏให้เห็นต่อ ADL เท่านั้น (อันที่จริงแล้ว IIRC บางส่วนขัดแย้งกับส่วนอื่น ๆ ในสเปคว่ามีการเพิ่มชื่อใด ๆ หรือไม่ แต่การเพิ่มชื่อจำเป็นต้องมีการตรวจสอบ namespace ดังนั้นในความเป็นจริงเป็นชื่อที่มองไม่เห็นมีการเพิ่ม. ดูหมายเหตุที่ 3.3.1p4)
Johannes Schaub - litb
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.