จะให้ฟังก์ชัน swap สำหรับคลาสของฉันได้อย่างไร?


89

อะไรคือวิธีที่เหมาะสมในการเปิดใช้swapอัลกอริทึมใน STL ของฉัน

1) swapสมาชิก ไม่std::swapใช้ SFINAE swapเคล็ดลับที่จะใช้สมาชิก

2) ยืนฟรีswapในเนมสเปซเดียวกัน

3) ความเชี่ยวชาญเฉพาะบางส่วนของstd::swap.

4) ทั้งหมดข้างต้น

ขอขอบคุณ.

แก้ไข: ดูเหมือนว่าฉันไม่ได้พูดคำถามของฉันอย่างชัดเจน โดยทั่วไปฉันมีคลาสเทมเพลตและฉันต้องการ STL algos เพื่อใช้วิธีการแลกเปลี่ยน (ที่มีประสิทธิภาพ) ที่ฉันเขียนสำหรับคลาสนั้น

คำตอบ:


95
  1. เป็นที่เหมาะสมการใช้งานswapของ เขียนมันด้วยวิธีนี้เมื่อคุณเขียน "ห้องสมุด" รหัสและต้องการเปิดใช้งาน ADL (ค้นหาโต้แย้งขึ้นอยู่กับ) swapบน นอกจากนี้สิ่งนี้ไม่เกี่ยวข้องกับ SFINAE
// some algorithm in your code
template<class T>
void foo(T& lhs, T& rhs) {
    using std::swap; // enable 'std::swap' to be found
                    // if no other 'swap' is found through ADL
    // some code ...
    swap(lhs, rhs); // unqualified call, uses ADL and finds a fitting 'swap'
                    // or falls back on 'std::swap'
    // more code ...
}
  1. เป็นวิธีที่เหมาะสมในการจัดเตรียมswapฟังก์ชันสำหรับชั้นเรียนของคุณ
namespace Foo {

class Bar{}; // dummy

void swap(Bar& lhs, Bar& rhs) {
    // ...
}

}

ถ้าswapตอนนี้ใช้ดังแสดงใน 1) ฟังก์ชันของคุณจะพบ นอกจากนี้คุณอาจทำให้ฟังก์ชันนั้นเป็นเพื่อนได้หากคุณต้องการหรือจัดหาสมาชิกswapที่เรียกโดยฟังก์ชันฟรี:

// version 1
class Bar{
public:
    friend void swap(Bar& lhs, Bar& rhs) {
    // ....
    }
};

// version 2
class Bar{
public:
    void swap(Bar& other) {
    // ...
    }
};

void swap(Bar& lhs, Bar& rhs) {
    lhs.swap(rhs);
}

...
  1. คุณหมายถึงความเชี่ยวชาญที่ชัดเจน บางส่วนยังคงเป็นอย่างอื่นและไม่สามารถทำได้สำหรับฟังก์ชันมีเพียงโครงสร้าง / คลาสเท่านั้น ด้วยเหตุนี้เนื่องจากคุณไม่สามารถเชี่ยวชาญstd::swapในคลาสเทมเพลตได้คุณจึงต้องจัดเตรียมฟังก์ชันฟรีในเนมสเปซของคุณ ไม่ใช่เรื่องเลวร้ายถ้าฉันจะพูดอย่างนั้น ตอนนี้ความเชี่ยวชาญเฉพาะทางที่ชัดเจนก็เป็นไปได้เช่นกัน แต่โดยทั่วไปคุณไม่ต้องการเชี่ยวชาญเทมเพลตฟังก์ชัน :
namespace std
{  // only allowed to extend namespace std with specializations

template<> // specialization
void swap<Bar>(Bar& lhs, Bar& rhs) noexcept {
    // ...
}

}
  1. ไม่เป็น 1) แตกต่างจาก 2) และ 3) นอกจากนี้การมีทั้ง 2) และ 3) จะทำให้มี 2) เสมอเพราะมันเข้ากันได้ดีกว่า

8
(1) และคำถาม (1) ของคุณไม่เข้ากันจริงๆเว้นแต่ฉันจะอ่านผิด ยัง +1
Dennis Zickefoose

1
@Xeo. ขอบคุณสำหรับข้อมูล ฉันแก้ไขคำถามของฉัน STL ใช้ swap ตามที่อธิบายไว้ในกรณีที่ 1 หรือไม่
pic11

1
@pic: ใช่ STL จะใช้การแลกเปลี่ยน ADL ที่ฉันแสดงในข้อ 1) แต่เฉพาะในกรณีที่มีฟังก์ชั่นฟรีไม่ใช่เฉพาะฟังก์ชันสมาชิกเท่านั้น ดู 2) และ 3) ทั้งสองเวอร์ชันจะถูกเลือกโดยอัลกอริทึม ฉันขอแนะนำ 2) เนื่องจาก 3) ล้าสมัยและถือว่าเป็นการปฏิบัติที่ไม่ดี
Xeo

2
ความคิดเห็นในโค้ดส่วนแรกทำให้เข้าใจผิด using std::swap;ไม่เปิดใช้งาน ADL เพียงแค่อนุญาตให้คอมไพเลอร์ค้นหาstd::swapว่า ADL ไม่พบการโอเวอร์โหลดที่เหมาะสม
David Rodríguez - dribeas

6
คำตอบนี้ถูกต้องในทางเทคนิค แต่จำเป็นอย่างมากในการแก้ไขเพื่อความชัดเจน OP (1) ไม่ใช่คำตอบที่ถูกต้องเนื่องจากการอ่านอย่างรวดเร็วเกินไปในคำตอบนี้ดูเหมือนจะบ่งชี้ผิดพลาด
Howard Hinnant

1

ในการตอบโจทย์ EDIT โดยที่ชั้นเรียนอาจเป็นคลาสเทมเพลตคุณไม่จำเป็นต้องมีความเชี่ยวชาญพิเศษเลย พิจารณาชั้นเรียนเช่นนี้:

template <class T>
struct vec3
{
    T x,y,z;
};

คุณสามารถกำหนดคลาสเช่น:

vec3<float> a;
vec3<double> b;
vec3<int> c;

หากคุณต้องการสร้างฟังก์ชันเดียวเพื่อใช้งานทั้ง 3 swaps (ไม่ใช่ว่าคลาสตัวอย่างนี้รับประกัน) คุณทำเช่นเดียวกับที่ Xeo กล่าวในข้อ (2) ... โดยไม่มีความเชี่ยวชาญ แต่เพียงแค่สร้างฟังก์ชันเทมเพลตปกติ:

template <class T>
void swap(vec3<T> &a, vec3<T> &b)
{
    using std::swap;
    swap(a.x,b.x);
    swap(a.y,b.y);
    swap(a.z,b.z);
}

ฟังก์ชันเทมเพลตการแลกเปลี่ยนควรอยู่ในเนมสเปซเดียวกับคลาสที่คุณต้องการสลับ วิธีการต่อไปนี้จะค้นหาและใช้การสลับนั้นแม้ว่าคุณจะไม่ได้อ้างถึงเนมสเปซนั้นโดยใช้ ADL:

using std::swap;
swap(a,b);

0

ดูเหมือนว่า (2) ( สถานะอิสระswapในเนมสเปซเดียวกับที่มีการประกาศคลาสที่ผู้ใช้กำหนด ) เป็นวิธีเดียวที่อนุญาตในการจัดเตรียมswapคลาสที่ผู้ใช้กำหนดเองเนื่องจากการเพิ่มการประกาศในเนมสเปซstdโดยทั่วไปเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ การขยายเนมสเปซ std (cppreference.com) :

เป็นพฤติกรรมที่ไม่ได้กำหนดในการเพิ่มการประกาศหรือคำจำกัดความให้กับเนมสเปซstdหรือในเนมสเปซใด ๆ ที่ซ้อนอยู่ภายในstdโดยมีข้อยกเว้นบางประการที่ระบุไว้ด้านล่าง

และswapไม่ได้แสดงว่าเป็นหนึ่งในข้อยกเว้นเหล่านั้น ดังนั้นการเพิ่มswapโอเวอร์โหลดของคุณเองลงในstdเนมสเปซจึงเป็นพฤติกรรมที่ไม่ได้กำหนด

นอกจากนี้ยังกล่าวว่าห้องสมุดมาตรฐานใช้โทรไม่เหมาะสมกับswapฟังก์ชั่นเพื่อการโทรที่ผู้ใช้กำหนดswapสำหรับการเรียนของผู้ใช้เช่นถ้าผู้ใช้กำหนดswapให้บริการ

ถอดเปลี่ยนได้ (cppreference.com) :

ฟังก์ชั่นมาตรฐานห้องสมุดหลายคน (เช่นขั้นตอนวิธีการอีกหลายคน) คาดว่าขัดแย้งของพวกเขาที่จะตอบสนองถอดเปลี่ยนได้ทันทีusing std::swap; swap(t, u);ซึ่งหมายความว่าทุกครั้งที่ดำเนินการมาตรฐานห้องสมุดแลกเปลี่ยนจะใช้เทียบเท่า

แลกเปลี่ยน (www.cplusplus.com) :

คอมโพเนนต์จำนวนมากของไลบรารีมาตรฐาน (ภายในstd) เรียกใช้swapในลักษณะที่ไม่มีเงื่อนไขเพื่ออนุญาตให้มีการเรียกโอเวอร์โหลดแบบกำหนดเองสำหรับประเภทที่ไม่ใช่พื้นฐานแทนเวอร์ชันทั่วไปนี้: การโอเวอร์โหลดแบบกำหนดเองของการswapประกาศในเนมสเปซเดียวกันกับประเภทที่ระบุไว้จะถูกเลือก ผ่านการค้นหาขึ้นอยู่กับอาร์กิวเมนต์ในเวอร์ชันทั่วไปนี้

แต่โปรดทราบว่าการใช้std::swapฟังก์ชันโดยตรงสำหรับคลาสที่ผู้ใช้กำหนดเองจะเรียกใช้เวอร์ชันทั่วไปstd::swapแทนการกำหนดโดยผู้ใช้swap:

my::object a, b;
std::swap(a, b); // calls std::swap, not my::swap

ดังนั้นขอแนะนำให้เรียกใช้swapฟังก์ชันในรหัสผู้ใช้ในลักษณะเดียวกับที่ทำในไลบรารีมาตรฐาน:

my::object a, b;
using std::swap;
swap(a, b); // calls my::swap if it is defined, or std::swap if it is not.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.