Không tìm thấy toán tử == khi so sánh cấu trúc trong C ++


96

So sánh hai trường hợp của cấu trúc sau, tôi nhận được lỗi:

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;
};

Lỗi là:

lỗi C2678: binary '==': không tìm thấy toán tử nào nhận toán hạng bên trái thuộc loại 'myproj :: MyStruct1' (hoặc không có chuyển đổi được chấp nhận)

Tại sao?

Câu trả lời:


126

Trong C ++, structs không có toán tử so sánh được tạo theo mặc định. Bạn cần viết của riêng bạn:

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

21
@Jonathan: Tại sao C ++ lại biết cách bạn muốn so sánh các structs của mình để bình đẳng? Và nếu bạn muốn một cách đơn giản, luôn memcmpcó khoảng thời gian dài như vậy cấu trúc của bạn không chứa con trỏ.
Xeo

12
@Xeo: memcmpkhông thành công với std::stringcấu trúc không phải POD (like ) và cấu trúc đệm.
fredoverflow

16
@Jonathan Các ngôn ngữ "hiện đại" mà tôi biết cung cấp một ==toán tử --- với một ngữ nghĩa gần như không bao giờ là những gì mong muốn. (Và họ không cung cấp phương tiện ghi đè nó, vì vậy bạn sẽ phải sử dụng một hàm thành viên). Các ngôn ngữ "hiện đại" mà tôi biết cũng không cung cấp ngữ nghĩa giá trị, vì vậy bạn buộc phải sử dụng con trỏ, ngay cả khi chúng không thích hợp.
James Kanze

4
@Jonathan Các trường hợp chắc chắn khác nhau, ngay cả trong một chương trình nhất định. Đối với các đối tượng thực thể, giải pháp được cung cấp bởi Java hoạt động rất tốt (và tất nhiên, bạn có thể làm chính xác điều tương tự trong C ++ --- thậm chí đó là C ++ thành ngữ cho các đối tượng thực thể). Câu hỏi là phải làm gì với các đối tượng giá trị. C ++ cung cấp một mặc định operator=(ngay cả khi nó thường làm sai), vì lý do tương thích với C. operator==Tuy nhiên, khả năng tương thích C không yêu cầu . Trên toàn cầu, tôi thích những gì C ++ làm hơn những gì Java làm. (Tôi không biết C #, vì vậy có lẽ điều đó tốt hơn.)
James Kanze

9
Ít nhất nó phải có thể với = defaultnó!
user362515 16/02/16

93

C ++ 20 đã giới thiệu các phép so sánh mặc định, hay còn gọi là "tàu vũ trụ"operator<=> , cho phép bạn yêu cầu các toán tử </ <=/ ==/ !=/ >=/ và / hoặc do trình biên dịch tạo ra >với việc triển khai rõ ràng / ngây thơ (?) ...

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

... nhưng bạn có thể tùy chỉnh điều đó cho các tình huống phức tạp hơn (thảo luận bên dưới). Xem tại đây để biết đề xuất ngôn ngữ, trong đó có các biện minh và thảo luận. Câu trả lời này vẫn phù hợp với C ++ 17 trở về trước và để biết rõ khi nào bạn nên tùy chỉnh việc triển khai operator<=>....

Có vẻ hơi vô ích khi C ++ chưa Chuẩn hóa điều này sớm hơn, nhưng thường cấu trúc / lớp có một số thành viên dữ liệu để loại trừ khỏi so sánh (ví dụ: bộ đếm, kết quả được lưu trong bộ nhớ cache, dung lượng vùng chứa, thành công của hoạt động cuối cùng / mã lỗi, con trỏ), như cũng như các quyết định cần đưa ra về vô số điều bao gồm nhưng không giới hạn ở:

  • trường nào cần so sánh trước, ví dụ: so sánh một intthành viên cụ thể có thể loại bỏ rất nhanh 99% các đối tượng không bằng nhau, trong khi một map<string,string>thành viên thường có các mục giống nhau và tương đối đắt để so sánh - nếu các giá trị được tải trong thời gian chạy, lập trình viên có thể hiểu rõ về trình biên dịch không thể
  • trong việc so sánh các chuỗi: phân biệt chữ hoa chữ thường, sự tương đương của khoảng trắng và dấu phân cách, thoát quy ước ...
  • độ chính xác khi so sánh phao / đôi
  • liệu các giá trị dấu phẩy động NaN có được coi là bằng nhau hay không
  • so sánh con trỏ hoặc trỏ đến dữ liệu (và nếu là con trỏ, làm thế nào để biết liệu con trỏ có đến mảng hay không và có bao nhiêu đối tượng / byte cần so sánh)
  • cho dù vấn đề trật tự khi so sánh container không được phân loại (ví dụ vector, list), và nếu như vậy cho dù đó là ok để sắp xếp chúng tại chỗ trước khi so sánh vs sử dụng thêm bộ nhớ để temporaries loại mỗi lần một so sánh được thực hiện
  • có bao nhiêu phần tử mảng hiện đang giữ các giá trị hợp lệ cần được so sánh (có kích thước ở đâu đó hay là một trạm gác không?)
  • thành viên nào của a unionđể so sánh
  • chuẩn hóa: ví dụ: các loại ngày có thể cho phép ngày trong tháng hoặc tháng trong năm nằm ngoài phạm vi hoặc một đối tượng hợp lý / phân số có thể có 6/8 trong khi đối tượng khác có 3/4, vì lý do hiệu suất, họ sửa lười biếng với một bước chuẩn hóa riêng biệt; bạn có thể phải quyết định xem có nên kích hoạt chuẩn hóa hay không trước khi so sánh
  • phải làm gì khi con trỏ yếu không hợp lệ
  • cách xử lý các thành viên và cơ sở không operator==tự triển khai (nhưng có thể có compare()hoặc operator<hoặc str()hoặc có ...)
  • những gì khóa phải được thực hiện trong khi đọc / so sánh dữ liệu mà các chuỗi khác có thể muốn cập nhật

Vì vậy, thật tuyệt khi có lỗi cho đến khi bạn nghĩ rõ ràng về ý nghĩa của so sánh đối với cấu trúc cụ thể của bạn, thay vì để nó biên dịch nhưng không cung cấp cho bạn kết quả có ý nghĩa tại thời điểm chạy .

Tất cả những gì đã nói, sẽ rất tốt nếu C ++ cho phép bạn nói bool operator==() const = default;khi bạn quyết định một ==bài kiểm tra từng thành viên "ngây thơ" ổn. Tương tự cho !=. Với nhiều thành viên / cơ sở, "mặc định" <, <=, >, và >=triển khai dường như vô vọng mặc dù - tầng trên cơ sở thứ tự của lời tuyên bố có thể nhưng rất khó có khả năng được những gì mong muốn, cho mâu thuẫn mệnh lệnh cho đặt hàng thành viên (căn cứ là nhất thiết trước khi các thành viên, nhóm bởi khả năng tiếp cận, xây dựng / phá hủy trước khi sử dụng phụ thuộc). Để trở nên hữu ích hơn, C ++ sẽ cần một hệ thống chú thích thành viên / cơ sở dữ liệu mới để hướng dẫn các lựa chọn - đó sẽ là một điều tuyệt vời để có trong Tiêu chuẩn, lý tưởng là cùng với việc tạo mã dựa trên AST do người dùng xác định ... Tôi mong đợi nó '

Thực hiện điển hình của các toán tử bình đẳng

Một triển khai hợp lý

thể một cách triển khai hợp lý và hiệu quả sẽ là:

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

Lưu ý rằng điều này cần một operator==cho MyStruct2quá.

Ý nghĩa của việc triển khai này và các lựa chọn thay thế, được thảo luận trong tiêu đề Thảo luận về các chi tiết cụ thể của MyStruct1 của bạn bên dưới.

Một cách tiếp cận nhất quán đối với ==, <,> <= vv

Thật dễ dàng sử dụng std::tuplecác toán tử so sánh để so sánh các cá thể lớp của riêng bạn - chỉ cần sử dụng std::tieđể tạo các bộ tham chiếu đến các trường theo thứ tự so sánh mong muốn. Tổng quát hóa ví dụ của tôi từ đây :

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...

Khi bạn "sở hữu" (nghĩa là có thể chỉnh sửa, một hệ số với lib của công ty và bên thứ 3) lớp bạn muốn so sánh, và đặc biệt là với sự sẵn sàng của C ++ 14 để suy ra kiểu trả về hàm từ returncâu lệnh, việc thêm một " tie "chức năng thành viên với lớp bạn muốn để có thể so sánh:

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

Sau đó, các so sánh ở trên đơn giản hóa thành:

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

Nếu bạn muốn có một tập hợp đầy đủ hơn các toán tử so sánh, tôi đề xuất các toán tử tăng cường (tìm kiếm less_than_comparable). Nếu nó không phù hợp vì lý do nào đó, bạn có thể thích hoặc không thích ý tưởng hỗ trợ macro (trực tuyến) :

#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)

... sau đó có thể được sử dụng ...

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

(Phiên bản C ++ 14 thành viên ràng buộc tại đây )

Thảo luận về các chi tiết cụ thể của MyStruct1 của bạn

Có những tác động đối với lựa chọn cung cấp một thành viên độc lập so với operator==()...

Triển khai tự do

Bạn có một quyết định thú vị để thực hiện. Vì lớp của bạn có thể được xây dựng ngầm từ một MyStruct2, một hàm độc lập / không phải thành viên bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)sẽ hỗ trợ ...

my_MyStruct2 == my_MyStruct1

... bằng cách đầu tiên tạo một MyStruct1từ tạm thời my_myStruct2, sau đó thực hiện so sánh. Điều này chắc chắn sẽ MyStruct1::an_intđược đặt thành giá trị tham số mặc định của hàm tạo là -1. Tùy thuộc vào việc bạn bao gồm an_intso sánh trong việc thực hiện của bạn operator==, một MyStruct1sức mạnh hoặc có thể không so sánh tương đương với một MyStruct2mà bản thân so sánh tương đương với MyStruct1của my_struct_2thành viên! Hơn nữa, tạo một thành viên tạm thời MyStruct1có thể là một hoạt động rất kém hiệu quả, vì nó liên quan đến việc sao chép my_struct2thành viên hiện có thành một thành viên tạm thời, chỉ để vứt bỏ nó sau khi so sánh. (Tất nhiên, bạn có thể ngăn chặn cấu trúc ngầm định này của MyStruct1s để so sánh bằng cách tạo hàm tạo đó explicithoặc loại bỏ giá trị mặc định cho an_int.)

Thành viên thực hiện

Nếu bạn muốn tránh cấu trúc ngầm của a MyStruct1từ a MyStruct2, hãy đặt toán tử so sánh thành một hàm thành viên:

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

Lưu ý rằng consttừ khóa - chỉ cần thiết cho việc triển khai thành viên - khuyên trình biên dịch rằng việc so sánh các đối tượng không sửa đổi chúng, vì vậy có thể được phép trên constcác đối tượng.

So sánh các biểu diễn hiển thị

Đôi khi cách dễ nhất để có được kiểu so sánh bạn muốn có thể là ...

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

... mà thường cũng rất đắt - những thứ đó stringđược tạo ra một cách đau đớn chỉ để vứt bỏ! Đối với các loại có giá trị dấu phẩy động, so sánh các biểu diễn hiển thị có nghĩa là số chữ số được hiển thị xác định dung sai trong đó các giá trị gần bằng nhau được coi là bằng nhau trong quá trình so sánh.


Thực ra đối với các toán tử so sánh <,>, <=,> = nó chỉ được yêu cầu triển khai <. Phần còn lại theo sau và không có cách nào có ý nghĩa để triển khai chúng có nghĩa là bất kỳ điều gì khác với việc triển khai có thể được tạo tự động. Thật kỳ lạ khi bạn phải tự mình thực hiện tất cả chúng.
André

@ André: thường xuyên hơn một tay viết int cmp(x, y)hoặc comparehàm trả về một giá trị âm cho x < y, 0 cho bình đẳng và một giá trị tích cực cho x > yđược sử dụng làm cơ sở cho <, >, <=, >=, ==, và !=; rất dễ dàng sử dụng CRTP để đưa tất cả các toán tử đó vào một lớp. Tôi chắc chắn rằng tôi đã đăng triển khai trong một câu trả lời cũ, nhưng không thể tìm thấy nó nhanh chóng.
Tony Delroy

@TonyD Chắc chắn bạn có thể làm điều đó, nhưng nó chỉ là dễ dàng để thực hiện >, <=>=về <. Bạn có thể cũng thực hiện ==!=theo cách đó, nhưng điều đó sẽ thường không phải là một thực hiện rất hiệu quả tôi đoán. Sẽ thật tuyệt nếu không cần CRTP hoặc các thủ thuật khác cho tất cả những điều này, nhưng tiêu chuẩn sẽ chỉ bắt buộc tự động tạo các toán tử này nếu người dùng không xác định rõ ràng và <được định nghĩa.
André

@ André: đó là bởi vì ==!=có thể không được thể hiện hiệu quả bằng <cách sử dụng so sánh cho mọi thứ là phổ biến. "Nó sẽ được tốt đẹp nếu không có CRTP hoặc thủ đoạn khác sẽ là cần thiết" - có lẽ, nhưng sau đó CRTP có thể dễ dàng được sử dụng để tạo ra rất nhiều nhà khai thác khác (ví dụ Bitwise |, &, ^từ |=, &=^=; + - * / %từ hình thức nhiệm vụ của mình; nhị phân -từ phủ định unary và +) - rất nhiều biến thể hữu ích tiềm năng trên chủ đề này mà chỉ cung cấp một tính năng ngôn ngữ cho một phần khá tùy ý của nó không phải là đặc biệt thanh lịch.
Tony Delroy

Bạn có phiền khi thêm vào Một triển khai hợp lý một phiên bản dùng std::tieđể thực hiện việc so sánh nhiều thành viên không?
NathanOliver

17

Bạn cần xác định rõ ràng operator ==cho MyStruct1.

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

Bây giờ so sánh == là hợp pháp cho 2 đối tượng như vậy.


11

Bắt đầu bằng C ++ 20, chúng ta có thể để thêm một bộ đầy đủ các toán tử so sánh mặc định ( ==, <=, vv) để một lớp bằng cách tuyên bố một mặc định toán tử so sánh ba chiều ( "tàu vũ trụ" nhà điều hành), như sau:

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

Với trình biên dịch C ++ 20 tuân thủ, việc thêm dòng đó vào MyStruct1 và MyStruct2 có thể đủ để cho phép so sánh bình đẳng, giả sử định nghĩa của MyStruct2 là tương thích.


2

So sánh không hoạt động trên cấu trúc trong C hoặc C ++. Thay vào đó hãy so sánh theo các trường.


2

Theo mặc định, cấu trúc không có ==toán tử. Bạn sẽ phải viết triển khai của riêng mình:

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

0

Ngoài ra, toán tử == chỉ hoạt động cho các nguyên thủy. Để mã hoạt động, bạn cần nạp chồng toán tử == cho cấu trúc của mình.


0

Vì bạn đã không viết toán tử so sánh cho cấu trúc của mình. Trình biên dịch không tạo ra nó cho bạn, vì vậy nếu bạn muốn so sánh, bạn phải tự viết nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.