ไม่พบตัวดำเนินการ == ขณะเปรียบเทียบโครงสร้างใน C ++


101

เมื่อเปรียบเทียบสองอินสแตนซ์ของโครงสร้างต่อไปนี้ฉันได้รับข้อผิดพลาด:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

ข้อผิดพลาดคือ:

ข้อผิดพลาด C2678: binary '==': ไม่พบตัวดำเนินการที่ใช้ตัวถูกดำเนินการด้านซ้ายประเภท 'myproj :: MyStruct1' (หรือไม่มีการแปลงที่ยอมรับได้)

ทำไม?

คำตอบ:


131

ใน C ++ structไม่มีตัวดำเนินการเปรียบเทียบที่สร้างขึ้นโดยค่าเริ่มต้น คุณต้องเขียนของคุณเอง:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}

21
@ โจนาธาน: ทำไม C ++ ถึงรู้ว่าคุณต้องการเปรียบเทียบstructความเท่าเทียมกันอย่างไร? และถ้าคุณต้องการวิธีง่ายๆก็มักจะmemcmpมีโครงสร้างของคุณที่ไม่มีตัวชี้อยู่นาน
Xeo

13
@Xeo: memcmpล้มเหลวกับสมาชิกที่ไม่ใช่ POD (เช่นstd::string) และโครงสร้างเบาะ
fredoverflow

16
@ โจนาธานภาษา "สมัยใหม่" ที่ฉันรู้จักให้ตัว==ดำเนินการ --- ด้วยความหมายที่แทบไม่เคยเป็นสิ่งที่ต้องการ (และพวกเขาไม่ได้ให้วิธีการลบล้างดังนั้นคุณต้องใช้ฟังก์ชันสมาชิก) ภาษา "สมัยใหม่" ที่ฉันรู้ก็ไม่ได้ให้ความหมายเชิงคุณค่าดังนั้นคุณจึงบังคับให้ใช้พอยน์เตอร์แม้ว่าจะไม่เหมาะสมก็ตาม
James Kanze

4
@Jonathan Case นั้นแตกต่างกันไปแม้จะอยู่ในโปรแกรมที่กำหนดก็ตาม สำหรับเอนทิตีอ็อบเจ็กต์โซลูชันที่จัดเตรียมโดย Java จะทำงานได้ดีมาก (และแน่นอนคุณสามารถทำสิ่งเดียวกันใน C ++ ได้ทุกอย่าง - แม้กระทั่ง C ++ ที่เป็นสำนวนสำหรับอ็อบเจ็กต์เอนทิตี) คำถามคือจะทำอย่างไรกับออบเจ็กต์มูลค่า C ++ เป็นค่าเริ่มต้นoperator=(แม้ว่าจะทำผิดบ่อยครั้งก็ตาม) ด้วยเหตุผลของความเข้ากันได้ของ C ความเข้ากันได้ของ C ไม่จำเป็นต้องมีoperator==. ทั่วโลกฉันชอบสิ่งที่ C ++ ทำกับสิ่งที่ Java ทำมากกว่า (ฉันไม่รู้ C # ดังนั้นอาจจะดีกว่านี้)
James Kanze

10
อย่างน้อยก็ควรจะเป็นไปได้= defaultมัน!
user362515

99

C ++ 20 แนะนำการเปรียบเทียบเริ่มต้นหรือที่เรียกว่า "ยานอวกาศ"operator<=>ซึ่งช่วยให้คุณสามารถร้องขอ</ <=/ ==/ !=/ >=/ และ / หรือ>ตัวดำเนินการที่สร้างโดยคอมไพเลอร์ด้วยการใช้งานที่ชัดเจน / ไร้เดียงสา (?) ...

auto operator<=>(const MyClass&) const = default;

... แต่คุณสามารถปรับแต่งสำหรับสถานการณ์ที่ซับซ้อนมากขึ้นได้ (อธิบายไว้ด้านล่าง) ดูข้อเสนอด้านภาษาที่นี่ซึ่งมีเหตุผลและการอภิปราย คำตอบนี้ยังคงเกี่ยวข้องกับ C ++ 17 และรุ่นก่อนหน้าและสำหรับข้อมูลเชิงลึกว่าคุณควรปรับแต่งการใช้งานเมื่อใดoperator<=> ...

อาจดูเหมือนไม่เป็นประโยชน์เล็กน้อยที่ C ++ ไม่ได้กำหนดมาตรฐานไว้ก่อนหน้านี้ แต่บ่อยครั้งโครงสร้าง / คลาสจะมีสมาชิกข้อมูลบางส่วนที่จะแยกออกจากการเปรียบเทียบ (เช่นตัวนับ, ผลลัพธ์ที่แคช, ความจุของคอนเทนเนอร์, ความสำเร็จของการดำเนินการครั้งสุดท้าย / รหัสข้อผิดพลาด, เคอร์เซอร์) เช่น ตลอดจนการตัดสินใจเกี่ยวกับสิ่งต่างๆมากมายรวมถึง แต่ไม่ จำกัด เพียง:

  • ฟิลด์ใดที่จะเปรียบเทียบก่อนเช่นการเปรียบเทียบintสมาชิกโดยเฉพาะอาจกำจัด 99% ของวัตถุที่ไม่เท่ากันได้อย่างรวดเร็วในขณะที่กmap<string,string>สมาชิกมักมีรายการที่เหมือนกันและมีราคาค่อนข้างแพงในการเปรียบเทียบ - หากโหลดค่าที่รันไทม์โปรแกรมเมอร์อาจมีข้อมูลเชิงลึก คอมไพเลอร์ไม่สามารถเป็นไปได้
  • ในการเปรียบเทียบสตริง: ความไวของตัวพิมพ์เล็กและตัวพิมพ์ใหญ่, ความเท่ากันของช่องว่างและตัวคั่น, การหลีกเลี่ยงอนุสัญญา ...
  • ความแม่นยำเมื่อเปรียบเทียบลอย / คู่
  • ว่าค่าจุดลอยตัวของ NaN ควรถือว่าเท่ากันหรือไม่
  • การเปรียบเทียบพอยน์เตอร์หรือชี้ไปยังข้อมูล (และถ้าเป็นอย่างหลังจะรู้ได้อย่างไรว่าพอยน์เตอร์เป็นอาร์เรย์และจำนวนอ็อบเจ็กต์ / ไบต์ที่ต้องการการเปรียบเทียบ)
  • คำสั่งซื้อมีความสำคัญหรือไม่เมื่อเปรียบเทียบคอนเทนเนอร์ที่ไม่ได้เรียงลำดับ (เช่น vector ,list ) และถ้าเป็นเช่นนั้นไม่ว่าจะเป็น OK เพื่อจัดเรียงพวกเขาในสถานที่ก่อนที่จะเปรียบเทียบกับการใช้หน่วยความจำเสริมการชั่วคราวเรียงลำดับในแต่ละครั้งการเปรียบเทียบที่จะทำ
  • ในปัจจุบันมีองค์ประกอบอาร์เรย์จำนวนเท่าใดที่มีค่าที่ถูกต้องซึ่งควรนำมาเปรียบเทียบ (มีขนาดอยู่ที่ใดที่หนึ่งหรือมีแมวมอง?)
  • สมาชิกคนไหนของ unionจะเปรียบเทียบ
  • การทำให้เป็นมาตรฐาน: ตัวอย่างเช่นประเภทวันที่อาจอนุญาตให้อยู่นอกช่วงของวันของเดือนหรือเดือนของปีหรือวัตถุที่มีเหตุผล / เศษส่วนอาจมี 6 / 8th ในขณะที่อีกประเภทหนึ่งมี 3 / 4ers ซึ่งด้วยเหตุผลด้านประสิทธิภาพจึงถูกต้อง อย่างเกียจคร้านกับขั้นตอนการทำให้เป็นมาตรฐานแยกต่างหาก คุณอาจต้องตัดสินใจว่าจะเริ่มการทำให้เป็นมาตรฐานก่อนการเปรียบเทียบหรือไม่
  • จะทำอย่างไรเมื่อจุดอ่อนไม่ถูกต้อง
  • วิธีจัดการกับสมาชิกและฐานที่ไม่ได้ใช้operator==เอง (แต่อาจมีcompare()หรือoperator<หรือstr()หรือได้รับ ... )
  • สิ่งที่ต้องใช้ในขณะที่อ่าน / เปรียบเทียบข้อมูลที่เธรดอื่นอาจต้องการอัปเดต

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

ทั้งหมดที่กล่าวมาคงจะดีถ้า C ++ ให้คุณพูดbool operator==() const = default;เมื่อคุณตัดสินใจว่าจะ==ทำการทดสอบแบบสมาชิกโดยสมาชิกแบบ"ไร้เดียงสา" ก็โอเค เหมือนกันสำหรับ!=. ป.ร. ให้สมาชิกหลาย / เบส, "เริ่มต้น" <, <=, >และ>=การใช้งานดูเหมือนสิ้นหวังแม้ว่า - ซ้อนบนพื้นฐานของคำสั่งของการประกาศของไปได้ แต่ไม่น่าจะเป็นสิ่งที่อยากให้ความขัดแย้งตอบสนองความต้องการสำหรับการสั่งซื้อสมาชิก (ฐานเป็นจำเป็นต้องก่อนที่สมาชิกในการจัดกลุ่มโดย ความสามารถในการเข้าถึงการก่อสร้าง / การทำลายก่อนการใช้งาน) เพื่อให้เป็นประโยชน์อย่างกว้างขวางมากขึ้น C ++ จำเป็นต้องมีระบบคำอธิบายประกอบข้อมูลสมาชิก / ฐานข้อมูลใหม่เพื่อเป็นแนวทางในการเลือกซึ่งจะเป็นสิ่งที่ดีที่จะมีในมาตรฐานแม้ว่าจะควบคู่ไปกับการสร้างรหัสที่ผู้ใช้กำหนดโดย AST ... ฉันคาดหวัง มัน'

การใช้ตัวดำเนินการความเท่าเทียมกันโดยทั่วไป

การดำเนินการที่เป็นไปได้

เป็นไปได้ว่าการใช้งานที่สมเหตุสมผลและมีประสิทธิภาพจะเป็นดังนี้:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

โปรดทราบว่าสิ่งนี้ต้องการoperator==สำหรับMyStruct2เช่นกัน

ผลกระทบของการใช้งานนี้และทางเลือกอื่นจะกล่าวถึงภายใต้หัวข้อการอภิปรายเกี่ยวกับข้อมูลเฉพาะของ MyStruct1 ของคุณด้านล่าง

แนวทางที่สอดคล้องกับ ==, <,> <= ฯลฯ

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

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

เมื่อคุณ "เป็นเจ้าของ" (เช่นสามารถแก้ไขปัจจัยที่มี libs ขององค์กรและบุคคลที่สาม) คลาสที่คุณต้องการเปรียบเทียบและโดยเฉพาะอย่างยิ่งกับการเตรียมพร้อมของ C ++ 14 ในการอนุมานประเภทการส่งคืนฟังก์ชันจากreturnคำสั่งมักจะดีกว่าที่จะเพิ่ม " tie "ฟังก์ชันสมาชิกกับคลาสที่คุณต้องการเปรียบเทียบ:

auto tie() const { return std::tie(my_struct1, an_int); }

จากนั้นการเปรียบเทียบด้านบนทำให้ง่ายขึ้นเพื่อ:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

หากคุณต้องการชุดตัวดำเนินการเปรียบเทียบที่สมบูรณ์กว่านี้ฉันขอแนะนำตัวดำเนินการเพิ่ม (ค้นหาless_than_comparable) หากไม่เหมาะสมด้วยเหตุผลบางประการคุณอาจหรือไม่ชอบแนวคิดของมาโครการสนับสนุน(ออนไลน์) :

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

... ที่สามารถใช้ a la ...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(เวอร์ชันผูกสมาชิก C ++ 14 ที่นี่ )

การอภิปรายเฉพาะของ MyStruct1 ของคุณ

มีผลกระทบต่อการเลือกให้สมาชิกยืนฟรีกับสมาชิกoperator==()...

การใช้งานอิสระ

คุณมีการตัดสินใจที่น่าสนใจ เนื่องจากชั้นเรียนของคุณสามารถสร้างขึ้นโดยปริยายจาก a ฟังก์ชันMyStruct2ยืนอิสระ / ไม่เป็นสมาชิกbool operator==(const MyStruct2& lhs, const MyStruct2& rhs)จะรองรับ ...

my_MyStruct2 == my_MyStruct1

... โดยการสร้างชั่วคราวก่อนMyStruct1จากmy_myStruct2นั้นทำการเปรียบเทียบ นี้แน่นอนจะออกตั้งค่าพารามิเตอร์คอนสตรัคที่เริ่มต้นของMyStruct1::an_int -1ทั้งนี้ขึ้นอยู่กับว่าคุณรวมถึงan_intการเปรียบเทียบในการดำเนินการของคุณoperator==ที่MyStruct1อาจจะหรืออาจจะไม่เปรียบเทียบเท่ากับMyStruct2ว่าตัวเองเปรียบเทียบเท่ากับMyStruct1's my_struct_2สมาชิก! นอกจากนี้การสร้างชั่วคราวMyStruct1อาจเป็นการดำเนินการที่ไม่มีประสิทธิภาพมากเนื่องจากเกี่ยวข้องกับการคัดลอกmy_struct2สมาชิกที่มีอยู่ไปไว้ที่ชั่วคราวเพียงเพื่อทิ้งหลังจากการเปรียบเทียบ (แน่นอนคุณสามารถป้องกันการสร้างMyStruct1s โดยนัยนี้เพื่อเปรียบเทียบได้โดยการสร้างตัวสร้างนั้นexplicitหรือลบค่าเริ่มต้นสำหรับan_int)

การใช้งานสมาชิก

หากคุณต้องการหลีกเลี่ยงการสร้าง a โดยนัยMyStruct1จาก a MyStruct2ให้ทำให้ตัวดำเนินการเปรียบเทียบเป็นฟังก์ชันสมาชิก:

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

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

การเปรียบเทียบการแสดงที่มองเห็นได้

บางครั้งวิธีที่ง่ายที่สุดในการเปรียบเทียบแบบที่คุณต้องการก็คือ ...

    return lhs.to_string() == rhs.to_string();

... ซึ่งมักมีราคาแพงมากเช่นกัน - สิ่งเหล่าstringนี้สร้างขึ้นอย่างเจ็บปวดเพียงเพื่อจะถูกโยนทิ้ง! สำหรับประเภทที่มีค่าทศนิยมการเปรียบเทียบการแทนค่าที่มองเห็นได้หมายถึงจำนวนของตัวเลขที่แสดงจะเป็นตัวกำหนดความอดทนซึ่งค่าที่เกือบเท่ากันจะถือว่าเท่ากันในระหว่างการเปรียบเทียบ


ที่จริงแล้วสำหรับตัวดำเนินการเปรียบเทียบ <,>, <=,> = มันควรจะต้องใช้เท่านั้นในการใช้งาน <. ส่วนที่เหลือตามมาและไม่มีวิธีที่มีความหมายในการนำไปใช้ซึ่งมีความหมายแตกต่างไปจากการใช้งานที่สามารถสร้างขึ้นโดยอัตโนมัติ เป็นเรื่องแปลกที่คุณต้องดำเนินการด้วยตัวเองทั้งหมด
André

@ André: บ่อยขึ้นที่เขียนด้วยตนเองint cmp(x, y)หรือcompareฟังก์ชั่นที่กลับมาเป็นค่าลบสำหรับx < y, 0 เพื่อความเท่าเทียมกันและความคุ้มค่าในเชิงบวกสำหรับx > yใช้เป็นพื้นฐานสำหรับการ<, >, <=, >=, ==และ!=; มันง่ายมากที่จะใช้ CRTP เพื่อฉีดตัวดำเนินการทั้งหมดลงในคลาส ฉันแน่ใจว่าฉันได้โพสต์การใช้งานในคำตอบเก่าแล้ว แต่ไม่สามารถค้นหาได้อย่างรวดเร็ว
Tony Delroy

@TonyD แน่ใจว่าคุณสามารถทำเช่นนั้น แต่มันก็เป็นเพียงเป็นเรื่องง่ายที่จะใช้>, <=และในแง่ของ>= <คุณสามารถนำไปใช้==และ!=วิธีนั้นได้ แต่โดยปกติแล้วจะไม่ใช่การใช้งานที่มีประสิทธิภาพมากนัก คงจะดีถ้าไม่จำเป็นต้องใช้ CRTP หรือกลเม็ดอื่น ๆ สำหรับสิ่งนี้ทั้งหมด แต่มาตรฐานจะกำหนดให้สร้างตัวดำเนินการเหล่านี้โดยอัตโนมัติหากผู้ใช้ไม่ได้กำหนดไว้อย่างชัดเจนและ<มีการกำหนดไว้
André

@ André: เป็นเพราะ==และ!=อาจแสดงออกไม่ได้อย่างมีประสิทธิภาพโดย<ใช้การเปรียบเทียบสำหรับทุกสิ่งเป็นเรื่องธรรมดา "มันคงจะดีถ้าไม่มี CRTP หรือเทคนิคอื่น ๆ จะต้อง" - บางที แต่แล้ว CRTP สามารถนำมาใช้เพื่อสร้างจำนวนมากของผู้ประกอบการอื่น ๆ (เช่นบิต|, &, ^จาก|=, &=และ^=; + - * / %จากรูปแบบที่ได้รับมอบหมายของพวกเขา; ไบนารี-จากการปฏิเสธเอกและ+) - รูปแบบที่เป็นประโยชน์มากมายในชุดรูปแบบนี้ซึ่งเพียงแค่ให้คุณลักษณะทางภาษาสำหรับชิ้นส่วนที่ไม่ได้ใช้งานโดยพลการเพียงชิ้นเดียวนั้นไม่ได้หรูหราเป็นพิเศษ
Tony Delroy

คุณช่วยเพิ่มการใช้งานเวอร์ชันที่น่าเชื่อถือซึ่งใช้std::tieในการเปรียบเทียบสมาชิกหลาย ๆ คนได้หรือไม่?
NathanOliver

16

คุณจำเป็นต้องกำหนดอย่างชัดเจนสำหรับoperator ==MyStruct1

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

ตอนนี้การเปรียบเทียบ == เป็นสิ่งที่ถูกกฎหมายสำหรับ 2 วัตถุดังกล่าว


11

เริ่มต้นใน C ++ 20 มันควรจะเป็นไปได้ที่จะเพิ่มชุดเต็มของผู้ประกอบการเปรียบเทียบค่าเริ่มต้น ( ==, <=ฯลฯ ) ในชั้นเรียนโดยการประกาศดำเนินการเปรียบเทียบสามทางเริ่มต้น ( "ยานอวกาศ" ผู้ประกอบ) เช่นนี้

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

ด้วยคอมไพเลอร์ C ++ 20 ที่เข้ากันได้การเพิ่มบรรทัดนั้นใน MyStruct1 และ MyStruct2 อาจเพียงพอที่จะอนุญาตให้มีการเปรียบเทียบความเท่าเทียมกันโดยสมมติว่าคำจำกัดความของ MyStruct2 เข้ากันได้


2

โดยค่าเริ่มต้นโครงสร้างจะไม่มีตัว==ดำเนินการ คุณจะต้องเขียนการใช้งานของคุณเอง:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }

1

การเปรียบเทียบใช้ไม่ได้กับโครงสร้างใน C หรือ C ++ เปรียบเทียบตามเขตข้อมูลแทน


0

นอกกรอบตัวดำเนินการ == ใช้งานได้กับแบบดั้งเดิมเท่านั้น เพื่อให้โค้ดของคุณทำงานได้คุณต้องโอเวอร์โหลดโอเปอเรเตอร์ == สำหรับโครงสร้างของคุณ


0

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

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