ทำไมคอมไพเลอร์ C ++ ไม่ได้กำหนดโอเปอเรเตอร์ == และโอเปอเรเตอร์! =?


302

ฉันเป็นแฟนตัวยงของการให้คอมไพเลอร์ทำงานให้คุณได้มากที่สุด เมื่อเขียนคลาสง่าย ๆ คอมไพเลอร์สามารถให้สิ่งต่อไปนี้กับ 'ฟรี':

  • ตัวสร้างเริ่มต้น (ว่าง)
  • ตัวสร้างสำเนา
  • ผู้ทำลายล้าง
  • ผู้ประกอบการที่ได้รับมอบหมาย ( operator=)

แต่ก็ดูเหมือนจะไม่สามารถทำให้คุณดำเนินการเปรียบเทียบใด ๆ - เช่นหรือoperator== operator!=ตัวอย่างเช่น:

class foo
{
public:
    std::string str_;
    int n_;
};

foo f1;        // Works
foo f2(f1);    // Works
foo f3;
f3 = f2;       // Works

if (f3 == f2)  // Fails
{ }

if (f3 != f2)  // Fails
{ }

มีเหตุผลที่ดีสำหรับสิ่งนี้หรือไม่? เหตุใดการเปรียบเทียบแบบเปรียบเทียบระหว่างสมาชิกจึงเป็นปัญหา เห็นได้ชัดว่าถ้าชั้นจัดสรรหน่วยความจำแล้วคุณจะต้องระวัง แต่สำหรับชั้นเรียนที่เรียบง่ายแน่นอนผู้แปลสามารถทำสิ่งนี้ให้คุณ?


4
แน่นอนว่า destructor นั้นให้บริการฟรี
Johann Gerell

23
ในการพูดคุยครั้งล่าสุดของเขา Alex Stepanov ชี้ให้เห็นว่าเป็นความผิดพลาดที่จะไม่มีการตั้งค่าเริ่มต้นโดยอัตโนมัติ==ในลักษณะเดียวกับที่มีการมอบหมายอัตโนมัติเริ่มต้น ( =) ภายใต้เงื่อนไขบางประการ (อาร์กิวเมนต์เกี่ยวกับตัวชี้ไม่สอดคล้องเพราะตรรกะใช้ได้ทั้งสำหรับ=และ==และไม่เพียง แต่เป็นครั้งที่สอง)
alfC

2
@ เบ็คมันเป็นหนึ่งในซีรีย์ที่ A9: youtube.com/watch?v=k-meLQaYP5Yฉันจำไม่ได้ว่าพูดถึงเรื่องไหน นอกจากนี้ยังมีข้อเสนอที่ดูเหมือนว่ากำลังจะมาถึง C ++ 17 open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0221r0.html
alfC

1
@ เบ็คมันเป็นหนึ่งในครั้งแรกในซีรีย์ "การเขียนโปรแกรมที่มีประสิทธิภาพพร้อมกับส่วนประกอบ" หรือ "การสนทนาการเขียนโปรแกรม" ทั้งคู่ที่ A9 ซึ่งมีอยู่ใน Youtube
alfC

1
@becko ที่จริงมีคำตอบด้านล่างชี้ไปที่มุมมองของ Alex stackoverflow.com/a/23329089/225186
alfC

คำตอบ:


71

คอมไพเลอร์จะไม่ทราบว่าคุณต้องการเปรียบเทียบตัวชี้หรือการเปรียบเทียบแบบลึก (ภายใน)

มันปลอดภัยกว่าที่จะไม่ใช้มันและปล่อยให้โปรแกรมเมอร์ทำเอง จากนั้นพวกเขาสามารถตั้งสมมติฐานทั้งหมดที่พวกเขาชอบ


292
ปัญหานั้นไม่ได้หยุดจากการสร้างสำเนา ctor ซึ่งมันค่อนข้างอันตราย
MSalters

78
คัดลอกคอนสตรัคเตอร์ (และoperator=) โดยทั่วไปทำงานในบริบทเดียวกับโอเปอเรเตอร์การเปรียบเทียบ - นั่นคือมีความคาดหวังว่าหลังจากคุณดำเนินการa = bแล้วa == bเป็นจริง แน่นอนมันทำให้รู้สึกสำหรับคอมไพเลอร์ที่จะให้เริ่มต้นใช้ความหมายเดียวกันมูลค่ารวมมันไม่สำหรับoperator== operator=ฉันสงสัยว่า paercebal นั้นถูกต้องจริง ๆ แล้วในที่นี้operator=(และสำเนา ctor) มีไว้สำหรับความเข้ากันได้กับ C เท่านั้นและพวกเขาไม่ต้องการทำให้สถานการณ์แย่ลง
Pavel Minaev

46
-1 แน่นอนว่าคุณต้องการการเปรียบเทียบแบบลึกหากโปรแกรมเมอร์ต้องการการเปรียบเทียบตัวชี้เขาจะเขียน (& f1 == & f2)
Viktor Sehr

62
Viktor ฉันขอแนะนำให้คุณคิดถึงคำตอบของคุณอีกครั้ง ถ้า class Foo มี Bar * ดังนั้นผู้รวบรวมจะทราบได้อย่างไรว่าตัวดำเนินการ Foo :: == ต้องการเปรียบเทียบที่อยู่ของ Bar * หรือเนื้อหาของ Bar
ทำเครื่องหมายอินแกรม

46
@ Mark: หากมีตัวชี้การเปรียบเทียบค่าตัวชี้จะสมเหตุสมผล - หากมีตัวชี้การเปรียบเทียบค่าจะสมเหตุสมผล ในสถานการณ์พิเศษโปรแกรมเมอร์สามารถแทนที่ นี่เป็นเหมือนภาษาที่ทำการเปรียบเทียบระหว่าง ints และ pointer-to-ints
Eamon Nerbonne

317

อาร์กิวเมนต์ที่หากคอมไพเลอร์สามารถให้ตัวสร้างการคัดลอกเริ่มต้นมันควรจะสามารถให้เริ่มต้นที่คล้ายกันoperator==()ทำให้รู้สึกจำนวนหนึ่ง ฉันคิดว่าเหตุผลสำหรับการตัดสินใจที่จะไม่ให้ค่าเริ่มต้นที่คอมไพเลอร์สร้างขึ้นสำหรับผู้ประกอบการนี้สามารถคาดเดาได้จากสิ่งที่ Stroustrup กล่าวเกี่ยวกับตัวสร้างสำเนาเริ่มต้นใน "การออกแบบและวิวัฒนาการของ C ++" (มาตรา 11.4.1 - การควบคุมการคัดลอก) :

ฉันถือว่าเป็นการส่วนตัวที่โชคร้ายที่การทำสำเนาถูกกำหนดโดยค่าเริ่มต้นและฉันไม่อนุญาตให้คัดลอกวัตถุในหลาย ๆ คลาสของฉัน อย่างไรก็ตาม C ++ สืบทอดการกำหนดค่าเริ่มต้นและตัวสร้างการคัดลอกจาก C และใช้บ่อย

ดังนั้นแทนที่จะ "ทำไม C ++ ถึงไม่มีค่าเริ่มต้น operator==() " คำถามควรเป็น "ทำไม C ++ มีการกำหนดค่าเริ่มต้นและตัวสร้างการคัดลอก" ด้วยคำตอบที่ว่ารายการเหล่านั้นถูกรวมอย่างไม่เต็มใจโดย Stroustrup สำหรับความเข้ากันได้กับ C (อาจเป็นสาเหตุของหูดส่วนใหญ่ของ C ++ แต่อาจเป็นสาเหตุหลักของความนิยมของ C ++)

เพื่อจุดประสงค์ของฉันเองใน IDE ของฉันส่วนย่อยที่ฉันใช้สำหรับคลาสใหม่มีการประกาศสำหรับผู้ดำเนินการกำหนดค่าส่วนตัวและตัวสร้างการคัดลอกดังนั้นเมื่อฉันสร้างคลาสใหม่ขึ้นมาฉันจะไม่ได้รับค่าเริ่มต้นและการคัดลอก ของการดำเนินการเหล่านั้นจากprivate:ส่วนถ้าฉันต้องการรวบรวมเพื่อให้สามารถสร้างพวกเขาสำหรับฉัน


29
คำตอบที่ดี. ฉันแค่อยากจะชี้ให้เห็นว่าใน C ++ 11 แทนที่จะทำให้ตัวดำเนินการมอบหมายและคัดลอกคอนสตรัคเตอร์ส่วนตัวคุณสามารถลบออกได้อย่างสมบูรณ์เช่นนี้: Foo(const Foo&) = delete; // no copy constructorและFoo& Foo=(const Foo&) = delete; // no assignment operator
karadoc

9
"อย่างไรก็ตาม C ++ สืบทอดการกำหนดค่าเริ่มต้นและตัวสร้างการคัดลอกจาก C" ซึ่งไม่ได้หมายความว่าทำไมคุณต้องสร้าง C ++ ทั้งหมดด้วยวิธีนี้ พวกเขาควร จำกัด สิ่งนี้ไว้ที่ POD เก่าธรรมดาเพียงประเภทที่มีอยู่ใน C อยู่แล้วไม่มีอีกแล้ว
thesaint

3
ฉันสามารถเข้าใจได้อย่างชัดเจนว่าทำไม C ++ จึงสืบทอดพฤติกรรมเหล่านี้มาstructแต่ฉันหวังว่ามันจะมีclassพฤติกรรมที่แตกต่างออกไป ในกระบวนการมันจะให้ความแตกต่างที่มีความหมายมากขึ้นระหว่างstructและclassข้างการเข้าถึงเริ่มต้น
jamesdlin

@jamesdlin หากคุณต้องการกฎการปิดการใช้งานการประกาศโดยนัยและความหมายของ ctors และการกำหนดถ้ามีการประกาศ dtor จะเหมาะสมที่สุด
Deduplicator

1
ผมยังไม่เห็นอันตรายใด ๆ operator==ในการให้โปรแกรมเมอร์อย่างชัดเจนสั่งซื้อคอมไพเลอร์ในการสร้าง ณ จุดนี้มันเป็นเพียงซินแท็กซ์น้ำตาลสำหรับบางรหัสจานหม้อไอน้ำ หากคุณกลัวว่าวิธีนี้โปรแกรมเมอร์อาจมองข้ามตัวชี้บางตัวในคลาสของคลาสคุณสามารถเพิ่มเงื่อนไขที่มันสามารถทำงานกับชนิดและวัตถุดั้งเดิมที่มีตัวดำเนินการเท่าเทียมกันเท่านั้น ไม่มีเหตุผลที่จะไม่อนุญาตสิ่งนี้โดยสิ้นเชิง
NO_NAME

93

แม้แต่ใน C ++ 20 คอมไพเลอร์ก็ยังไม่สร้างoperator==ให้คุณโดยปริยาย

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

แต่คุณจะได้รับความสามารถในการเริ่มต้นอย่างชัดเจน== ตั้งแต่ C ++ 20 :

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

ผิดนัด==ไม่สมาชิกที่ชาญฉลาด==(ในลักษณะเดียวกับที่ตัวสร้างสำเนาเริ่มต้นไม่สมาชิกฉลาดก่อสร้างคัดลอก) กฎระเบียบใหม่ยังมีความสัมพันธ์ที่คาดว่าระหว่างและ== !=ตัวอย่างเช่นด้วยการประกาศข้างต้นฉันสามารถเขียนทั้งสอง:

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

คุณลักษณะเฉพาะนี้ (ผิดนัดoperator==และสมมาตรระหว่าง==และ!=) มาจากหนึ่งในข้อเสนอoperator<=>ที่เป็นส่วนหนึ่งของคุณลักษณะภาษาที่เป็นที่กว้างขึ้น


คุณรู้หรือไม่ว่ามีการอัปเดตล่าสุดนี้อีกหรือไม่ จะมีให้ใน c ++ 17 หรือไม่?
dcmm88

3
@ dcmm88 ขออภัยที่ไม่มีใน C ++ 17 ฉันได้อัพเดตคำตอบแล้ว
Anton Savin

2
ข้อเสนอที่ได้รับการแก้ไขซึ่งอนุญาตในสิ่งเดียวกัน (ยกเว้นรูปแบบย่อ) จะอยู่ใน C ++ 20 แม้ว่า :)
Rakete1111

ดังนั้นโดยทั่วไปคุณต้องระบุ= defaultสำหรับสิ่งที่ไม่ได้สร้างขึ้นตามค่าเริ่มต้นใช่ไหม ดูเหมือนว่า oxymoron กับฉัน ("ค่าเริ่มต้นชัดเจน")
artin

@artin มันสมเหตุสมผลแล้วที่การเพิ่มฟีเจอร์ใหม่ให้กับภาษาไม่ควรทำให้การใช้งานมีปัญหา การเพิ่มมาตรฐานไลบรารีใหม่หรือคอมไพเลอร์สิ่งใหม่สามารถทำได้เป็นสิ่งหนึ่ง การเพิ่มฟังก์ชั่นสมาชิกใหม่ที่ไม่เคยมีมาก่อนเป็นเรื่องที่แตกต่างกันโดยสิ้นเชิง เพื่อรักษาความปลอดภัยโครงการของคุณจากข้อผิดพลาดมันจะต้องใช้ความพยายามมากขึ้น ฉันเองต้องการธงคอมไพเลอร์เพื่อสลับระหว่างการเริ่มต้นที่ชัดเจนและโดยปริยาย คุณสร้างโครงการจากมาตรฐาน C ++ ที่เก่ากว่าใช้ค่าเริ่มต้นที่ชัดเจนโดยการตั้งค่าสถานะคอมไพเลอร์ คุณอัปเดตคอมไพเลอร์แล้วดังนั้นคุณควรกำหนดค่าให้เหมาะสม สำหรับโครงการใหม่ทำให้เป็นนัย
Maciej Załucki

44

IMHO ไม่มีเหตุผล "ดี" เหตุผลที่มีคนจำนวนมากที่เห็นด้วยกับการตัดสินใจออกแบบนี้ก็เพราะพวกเขาไม่ได้เรียนรู้ที่จะเชี่ยวชาญพลังของความหมายตามค่า ผู้คนจำเป็นต้องเขียนตัวสร้างสำเนาแบบกำหนดเองจำนวนมากตัวดำเนินการเปรียบเทียบและตัวทำลายเนื่องจากใช้ตัวชี้แบบดิบในการใช้งาน

เมื่อใช้พอยน์เตอร์สมาร์ทที่เหมาะสม (เช่น std :: shared_ptr) ตัวสร้างการคัดลอกเริ่มต้นมักจะใช้ได้


39

มันตอบ C ++ ไม่ได้ทำ == เพราะ C ไม่ได้และนี่คือเหตุผลว่าทำไม C ถึงมีค่าเริ่มต้นเท่านั้น = แต่ไม่มี == ในตอนแรก C ต้องการทำให้มันง่าย: C Implement = โดย memcpy; อย่างไรก็ตาม == ไม่สามารถใช้งานได้โดย memcmp เนื่องจากการเติมเต็ม เนื่องจากการเติมเต็มไม่ได้ถูกกำหนดค่าเริ่มต้น memcmp จึงบอกว่ามันต่างกันแม้ว่าจะเหมือนกันก็ตาม ปัญหาเดียวกันนี้เกิดขึ้นสำหรับคลาสที่ว่างเปล่า: memcmp บอกว่ามันแตกต่างกันเนื่องจากขนาดของคลาสที่ว่างเปล่าไม่เป็นศูนย์ จะเห็นได้จากด้านบนว่าการใช้ == นั้นซับซ้อนกว่าการใช้ = ใน C. ตัวอย่างโค้ดบางส่วนเกี่ยวกับเรื่องนี้ การแก้ไขของคุณจะได้รับการชื่นชมถ้าฉันผิด


6
C ++ ไม่ได้ใช้ memcpy สำหรับoperator=- ซึ่งจะใช้ได้กับประเภท POD เท่านั้น แต่ C ++ จะให้ค่าเริ่มต้นoperator=สำหรับประเภท POD ที่ไม่ใช่เช่นกัน
เฟล็กโซ

2
ใช่ C + + ใช้งาน = ในทางที่ซับซ้อนยิ่งขึ้น ดูเหมือนว่า C เพิ่งติดตั้ง = ด้วย memcpy แบบง่าย
Rio Wing

เนื้อหาของคำตอบนี้ควรอยู่ร่วมกับ Michael เขาแก้ไขคำถามแล้วคำตอบนี้มัน
Sgene9

27

ในวิดีโอนี้Alex Stepanov ผู้สร้าง STL ตอบคำถามนี้เมื่อเวลาประมาณ 13:00 น. เพื่อสรุปโดยดูวิวัฒนาการของ C ++ เขาให้เหตุผลว่า:

  • มันโชคร้ายที่== และ! =ไม่ได้ประกาศโดยปริยาย (และ Bjarne เห็นด้วยกับเขา) ภาษาที่ถูกต้องควรมีสิ่งเหล่านั้นพร้อมสำหรับคุณ (เขาดำเนินการต่อไปเพื่อแนะนำคุณไม่ควรกำหนด! =ซึ่งจะแบ่งความหมายของ== )
  • เหตุผลนี้เป็นกรณีที่มีราก (เป็นจำนวนมากของปัญหา C ++) ใน C. มีผู้ประกอบการที่ได้รับมอบหมายจะถูกกำหนดโดยปริยายด้วยบิตโดยการโอนบิตแต่ที่จะไม่ทำงานสำหรับ== สามารถอ่านคำอธิบายโดยละเอียดเพิ่มเติมได้ในบทความนี้จาก Bjarne Stroustrup
  • ในคำถามที่ตามมาทำไมเมื่อเปรียบเทียบสมาชิกไม่ได้ใช้เขาบอกว่าสิ่งที่น่าอัศจรรย์ : C เป็นภาษาพื้นบ้านและคนที่ใช้สิ่งเหล่านี้เพื่อริตชี่บอกเขาว่าเขาพบว่ามันยากที่จะติดตั้ง!

จากนั้นเขาก็บอกว่าในอนาคต (ไกล) ==และ! =จะถูกสร้างขึ้นโดยปริยาย


2
ดูเหมือนว่าอนาคตอันไกลโพ้นนี้จะไม่เป็นปี 2017 หรือ 18 หรือ 19 ดีที่คุณได้ล่องลอยฉัน ...
UmNyobe

18

C ++ 20 มีวิธีการใช้งานตัวดำเนินการเปรียบเทียบเริ่มต้น

ตัวอย่างจากcppreference.com :

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>

4
ฉันประหลาดใจที่พวกเขาใช้Pointเป็นตัวอย่างสำหรับการดำเนินการสั่งซื้อเนื่องจากไม่มีวิธีการเริ่มต้นที่สมเหตุสมผลในการสั่งซื้อจุดสองจุดด้วยกันxและyพิกัด ...
pipe

4
@pipe หากคุณไม่สนใจว่าองค์ประกอบใดเป็นลำดับการใช้ตัวดำเนินการเริ่มต้นก็สมเหตุสมผล ตัวอย่างเช่นคุณอาจใช้std::setเพื่อให้แน่ใจว่าคะแนนทั้งหมดนั้นไม่ซ้ำกันและstd::setใช้operator<เฉพาะ
vll

เกี่ยวกับประเภทผลตอบแทนauto: สำหรับกรณีนี้เราสามารถสันนิษฐานได้ว่ามันมาstd::strong_orderingจาก#include <compare>ไหน
kevinarpe

1
@kevinarpe ชนิดส่งคืนคือstd::common_comparison_category_tซึ่งสำหรับคลาสนี้จะเป็นการสั่งซื้อเริ่มต้น ( std::strong_ordering)
vll

15

มันเป็นไปไม่ได้ที่จะกำหนดค่าเริ่มต้น==แต่คุณสามารถกำหนดค่าเริ่มต้น!=ผ่าน==ที่คุณมักจะควรกำหนดด้วยตัวเอง สำหรับสิ่งนี้คุณควรทำสิ่งต่อไปนี้:

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

คุณสามารถดูhttp://www.cplusplus.com/reference/std/utility/rel_ops/สำหรับรายละเอียด

นอกจากนี้ถ้าคุณกำหนดoperator< ผู้ประกอบการสำหรับ <=,>,> = std::rel_opsสามารถสรุปได้จากมันเมื่อใช้

แต่คุณควรระวังเมื่อคุณใช้งานstd::rel_opsเนื่องจากตัวดำเนินการเปรียบเทียบสามารถอนุมานได้สำหรับประเภทที่คุณไม่คาดคิด

เพิ่มเติมวิธีการอนุมานผู้ประกอบการที่เกี่ยวข้องจากขั้นพื้นฐานหนึ่งที่แนะนำคือการใช้เพิ่ม :: ผู้ประกอบการ

วิธีที่ใช้ในการเพิ่มประสิทธิภาพจะดีกว่าเพราะจะกำหนดการใช้งานของโอเปอเรเตอร์สำหรับคลาสที่คุณต้องการเท่านั้นไม่ใช่สำหรับคลาสทั้งหมดที่อยู่ในขอบเขต

คุณยังสามารถสร้าง "+" จาก "+ =", - จาก "- =", ฯลฯ ... (ดูรายการทั้งหมดได้ที่นี่ )


ฉันไม่ได้รับค่าเริ่มต้น!=หลังจากเขียน==โอเปอเรเตอร์ หรือฉันทำ แต่มันก็ไม่มีconstลักษณะ ต้องเขียนด้วยตัวเองเช่นกันและทุกอย่างก็ดี
จอห์น

คุณสามารถเล่นกับเพื่อนเพื่อให้ได้ผลลัพธ์ที่ต้องการ หากไม่มีโค้ดมันเป็นการยากที่จะพูดในสิ่งที่ผิดปกติ
sergtk

2
มีเหตุผลrel_opsถูกเลิกใช้ใน C ++ 20: เพราะมันไม่ทำงานอย่างน้อยก็ไม่ได้ทุกที่และไม่สม่ำเสมออย่างแน่นอน ไม่มีวิธีที่เชื่อถือได้sort_decreasing()ในการรวบรวม ในทางตรงกันข้ามBoost.Operatorsทำงานและทำงานได้ตลอดเวลา
Barry

10

C ++ 0x มีข้อเสนอสำหรับฟังก์ชั่นเริ่มต้นดังนั้นคุณสามารถพูดได้ว่าdefault operator==; เราได้เรียนรู้ว่ามันช่วยทำให้สิ่งเหล่านี้ชัดเจน


3
ฉันคิดว่าเฉพาะ "ฟังก์ชั่นสมาชิกพิเศษ" (คอนสตรัคเตอร์เริ่มต้นคัดลอกคอนสตรัคเตอร์โอเปอเรเตอร์การมอบหมายและ destructor) เท่านั้นที่สามารถกำหนดค่าเริ่มต้นได้อย่างชัดเจน พวกเขาขยายสิ่งนี้ไปยังผู้ให้บริการรายอื่นหรือไม่?
Michael Burr

4
ย้ายคอนสตรัคนอกจากนี้ยังสามารถผิดนัด operator==แต่ฉันไม่คิดว่านี้จะนำไปใช้ ซึ่งเป็นที่น่าเสียดาย
Pavel Minaev

5

แนวคิดมันไม่ง่ายที่จะกำหนดความเท่าเทียมกัน แม้สำหรับข้อมูล POD ใคร ๆ ก็สามารถเถียงได้ว่าแม้ว่าเขตข้อมูลจะเหมือนกัน แต่มันเป็นวัตถุที่แตกต่างกัน ขึ้นอยู่กับการใช้งานของผู้ให้บริการ น่าเสียดายที่คอมไพเลอร์ของคุณไม่ใช่คนบ้าและไม่สามารถอนุมานได้

นอกจากนี้ฟังก์ชั่นเริ่มต้นยังเป็นวิธีที่ยอดเยี่ยมในการถ่ายภาพตัวเองด้วยการเดินเท้า ค่าเริ่มต้นที่คุณอธิบายนั้นโดยทั่วไปจะมีความเข้ากันได้กับ POD structs อย่างไรก็ตามพวกเขาทำมากกว่าความเสียหายที่เพียงพอกับนักพัฒนาลืมเกี่ยวกับพวกเขาหรือความหมายของการใช้งานเริ่มต้น


10
ไม่มีความกำกวมสำหรับ POD structs - พวกเขาควรประพฤติตนในลักษณะเดียวกับที่ POD ประเภทอื่นทำซึ่งก็คือความเท่าเทียมกันของมูลค่า หนึ่งที่intสร้างขึ้นผ่าน ctor คัดลอกจากที่อื่นจะเท่ากับหนึ่งที่มันถูกสร้างขึ้น; เหตุผลเดียวที่ต้องทำเพื่อstructสองintสิ่งคือการทำงานในลักษณะเดียวกัน
Pavel Minaev

1
@mgiuca: ฉันเห็นประโยชน์มากมายสำหรับความสัมพันธ์ที่เท่าเทียมกันแบบสากลที่จะอนุญาตให้ใช้ประเภทใด ๆ ที่ทำงานเป็นค่าที่จะใช้เป็นกุญแจสำคัญในพจนานุกรมหรือคอลเลกชันที่คล้ายกัน อย่างไรก็ตามคอลเลกชันดังกล่าวไม่สามารถทำงานได้อย่างเป็นประโยชน์หากไม่มีความสัมพันธ์แบบสมดุลที่สะท้อนกลับได้ IMHO ทางออกที่ดีที่สุดคือการกำหนดโอเปอเรเตอร์ใหม่ซึ่งชนิดบิวด์อินทั้งหมดสามารถนำมาใช้อย่างสมเหตุสมผลและกำหนดพอยน์เตอร์ชนิดใหม่บางอย่างที่เหมือนกับที่มีอยู่ยกเว้นว่าบางคนจะกำหนดความเท่าเทียมกัน ตัวดำเนินการเทียบเท่า
supercat

1
@supercat โดยการเปรียบเทียบคุณสามารถสร้างอาร์กิวเมนต์ที่เหมือนกันเกือบทั้งหมดสำหรับ+โอเปอเรเตอร์โดยที่ไม่ใช่แบบลอยตัว นั่นคือ(x + y) + z! = x + (y + z)เนื่องจากวิธีการปัดเศษของ FP ที่เกิดขึ้น (เบบี้ส์นี้เป็นปัญหาไกลยิ่งกว่า==เพราะมันเป็นความจริงสำหรับค่าตัวเลขตามปกติ.) คุณอาจแนะนำให้เพิ่มผู้ประกอบการนอกจากนี้ใหม่ว่างานทุกประเภทที่เป็นตัวเลข (แม้ int) และเกือบจะเหมือนกับ+แต่มันก็เป็นแบบเชื่อมโยง ( อย่างใด) แต่คุณจะเพิ่มการขยายตัวและความสับสนให้กับภาษาโดยไม่ต้องช่วยคนจำนวนมากจริงๆ
mgiuca

1
@mgiuca: การมีสิ่งต่าง ๆ ที่ค่อนข้างคล้ายกันยกเว้นกรณีขอบมักจะมีประโยชน์อย่างมากและการพยายามที่จะหลีกเลี่ยงสิ่งต่าง ๆ ส่งผลให้เกิดความซับซ้อนที่ไม่จำเป็น หากบางครั้งรหัสไคลเอนต์จำเป็นต้องมีการจัดการเคสแบบขอบด้านหนึ่งและบางครั้งจำเป็นต้องจัดการเคสอื่นการมีเมธอดสำหรับการจัดการแต่ละสไตล์จะกำจัดโค้ดการจัดการเคสขนาดใหญ่จำนวนมากในไคลเอ็นต์ สำหรับการเปรียบเทียบของคุณไม่มีวิธีกำหนดการดำเนินการเกี่ยวกับค่าจุดลอยตัวขนาดคงที่เพื่อให้ได้ผลลัพธ์สกรรมกริยาในทุกกรณี (แม้ว่าภาษา 1980 บางภาษามีความหมายดีกว่า ...
supercat

1
... มากกว่าวันนี้ในเรื่องนั้น) และทำให้ความจริงที่ว่าพวกเขาไม่ทำสิ่งที่เป็นไปไม่ได้ไม่น่าแปลกใจ อย่างไรก็ตามไม่มีอุปสรรคขั้นพื้นฐานในการใช้ความสัมพันธ์ที่เท่าเทียมกันซึ่งจะสามารถนำไปใช้กับค่านิยมประเภทใดก็ได้ที่สามารถคัดลอกได้ในระดับสากล
supercat

1

มีเหตุผลที่ดีสำหรับสิ่งนี้หรือไม่? เหตุใดการเปรียบเทียบแบบเปรียบเทียบระหว่างสมาชิกจึงเป็นปัญหา

มันอาจจะไม่เป็นปัญหาการใช้งาน แต่ในแง่ของประสิทธิภาพการเปรียบเทียบสมาชิกโดยสมาชิกเริ่มต้นมีแนวโน้มที่จะย่อยที่ดีที่สุดกว่าการมอบหมาย / คัดลอกสมาชิกโดยสมาชิกเริ่มต้น ลำดับการเปรียบเทียบส่งผลกระทบต่อประสิทธิภาพการทำงานต่างจากลำดับการมอบหมายเนื่องจากสมาชิกคนแรกที่ไม่เท่ากันแสดงว่าสามารถข้ามส่วนที่เหลือได้ ดังนั้นหากมีสมาชิกบางคนที่มักจะเท่ากับคุณต้องการเปรียบเทียบพวกเขาครั้งสุดท้ายและคอมไพเลอร์ไม่ทราบว่าสมาชิกคนใดมีแนวโน้มที่จะเท่ากัน

ลองพิจารณาตัวอย่างนี้โดยverboseDescriptionเลือกสตริงที่มีความยาวจากชุดคำอธิบายสภาพอากาศที่ค่อนข้างเล็ก

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(แน่นอนว่าคอมไพเลอร์จะมีสิทธิ์เพิกเฉยต่อคำสั่งของการเปรียบเทียบถ้ามันรับรู้ว่าพวกเขาไม่มีผลข้างเคียง แต่สันนิษฐานว่ามันยังคงใช้คิวจากซอร์สโค้ดซึ่งมันไม่มีข้อมูลที่ดีกว่าของมันเอง)


แต่ไม่มีใครขัดขวางคุณจากการเขียนการเปรียบเทียบที่ผู้ใช้กำหนดเองให้เหมาะสมหากคุณพบปัญหาด้านประสิทธิภาพ ในประสบการณ์ของฉันที่จะเป็นกรณีของชนกลุ่มน้อยที่จิ๋ว
Peter - Reinstate Monica

1

เพื่อให้คำตอบสำหรับคำถามนี้ยังคงสมบูรณ์เมื่อเวลาผ่านไป: ตั้งแต่ C ++ 20 สามารถสร้างขึ้นโดยอัตโนมัติด้วยคำสั่ง auto operator<=>(const foo&) const = default;

มันจะสร้างตัวดำเนินการทั้งหมด: ==,! =, <, <=,> และ> =, ดูhttps://en.cppreference.com/w/cpp/language/default_comparisonsสำหรับรายละเอียด

เนื่องจากรูปลักษณ์ของผู้ปฏิบัติ<=>งานจึงเรียกว่าผู้ดำเนินการยานอวกาศ ดูเพิ่มเติมที่เหตุใดเราจึงต้องการยานอวกาศ <=> โอเปอเรเตอร์ใน C ++ .

แก้ไข: ยังอยู่ใน C ++ 11 แทนเรียบร้อยสวยที่สามารถใช้ได้กับstd::tieดูhttps://en.cppreference.com/w/cpp/utility/tuple/tiebool operator<(…)สำหรับตัวอย่างรหัสที่สมบูรณ์แบบด้วย ส่วนที่น่าสนใจเปลี่ยนไปทำงานด้วย==คือ:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

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


-1

ฉันเห็นด้วยสำหรับคลาสประเภท POD แล้วคอมไพเลอร์สามารถทำเพื่อคุณ อย่างไรก็ตามสิ่งที่คุณอาจจะคิดว่าเรียบง่ายคอมไพเลอร์อาจจะผิด ดังนั้นจะเป็นการดีกว่าถ้าให้โปรแกรมเมอร์ทำ

ฉันมีกรณี POD หนึ่งครั้งที่มีสองฟิลด์ที่ไม่ซ้ำกันดังนั้นการเปรียบเทียบจะไม่ถือว่าเป็นจริง อย่างไรก็ตามการเปรียบเทียบที่ฉันต้องการเพียงแค่เปรียบเทียบกับเพย์โหลด - สิ่งที่คอมไพเลอร์จะไม่เข้าใจหรือเคยคิดด้วยตัวของมันเอง

นอกจากนี้ - พวกเขาใช้เวลาเขียนไม่นานเหรอ?!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.