Tại sao bất đẳng thức được kiểm tra là (! (A == b)) trong rất nhiều mã thư viện chuẩn C ++?


142

Đây là mã từ removemã thư viện chuẩn C ++ . Tại sao bất đẳng thức được kiểm tra if (!(*first == val))thay vì if (*first != val)?

 template <class ForwardIterator, class T>
      ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
 {
     ForwardIterator result = first;
     while (first!=last) {
         if (!(*first == val)) {
             *result = *first;
             ++result;
         }
         ++first;
     }
     return result;
 }

2
@BeyelerStudios có lẽ đúng. Nó cũng phổ biến khi thực hiện operator!=. Chỉ cần sử dụng operator==triển khai:bool operator!=(const Foo& other) { return !(*this == other); }
simon

1
thực sự tôi sửa câu nói của mình: các tham chiếu đề cập đến việc loại bỏ tất cả các yếu tố giá trị bằng nhau vì vậy operator==loại dự kiến ​​sẽ được sử dụng ở đây ...
BeyelerStudios

Ồ, và cũng nên có một conství dụ trong bình luận trước đây của tôi, nhưng bạn có được điểm. (Quá muộn để chỉnh sửa nó)
simon

Lý do cho điều này có liên quan đến một câu hỏi khác (về cơ bản có thể trả lời bằng "Không, không nhất thiết") và khái niệm về việc EqualityComparableHurkyl đã đề cập trong câu trả lời của mình .
Marco13

Câu trả lời:


144

Bởi vì điều này có nghĩa là yêu cầu duy nhất trên T là thực hiện một operator==. Bạn có thể yêu cầu T phải có một operator!=ý tưởng chung nhưng ở đây là bạn nên đặt càng ít gánh nặng cho người dùng mẫu càng tốt và các mẫu khác cần operator==.


13
mẫu <class T> toán tử bool nội tuyến! = <T a, T b> {return! (a == b); }
Joshua

8
Sẽ có bất kỳ kịch bản nào mà trình biên dịch sẽ không thể trao đổi tất cả các trường hợp của =! đến! (==)? Tại sao điều này chưa phải là hành động mặc định cho trình biên dịch thực hiện?
Aidan Gomez

20
@AidanGomez Để tốt hơn hay tồi tệ hơn, bạn có thể làm quá tải các toán tử để làm bất cứ điều gì bạn muốn. Nó không phải có ý nghĩa hoặc nhất quán.
Neil Kirk

10
x != ykhông được định nghĩa giống như !(x == y). Điều gì xảy ra nếu các toán tử này trả về cây phân tích của DSL nhúng?
Brice M. Dempsey

7
@Joshua Điều đó rất tệ nếu cố gắng sử dụng SFINAE để phát hiện xem có !=được hỗ trợ hay không (sẽ trả lại không đúng sự thật - ngay cả khi operator==không được hỗ trợ!). Tôi cũng lo lắng nó sẽ khiến một số việc sử dụng !=trở nên mơ hồ.

36

Hầu hết các chức năng trong STL chỉ hoạt động với operator<hoặc operator==. Điều này yêu cầu người dùng chỉ thực hiện hai toán tử này (hoặc đôi khi ít nhất một trong số họ). Ví dụ std::setsử dụng operator<(chính xác hơn std::lessmà gọioperator< theo mặc định) và không operator>quản lý đơn hàng. Các removemẫu trong ví dụ của bạn là một trường hợp tương tự - nó chỉ sử dụng operator==và không operator!=nên operator!=không cần phải được xác định.


2
Các hàm không sử dụng operator<trực tiếp mà thay vào đó sử dụng std::less, lần lượt mặc định là operator<.
Christian Hackl

1
Trên thực tế, có vẻ như các hàm thuật toán tiêu chuẩn, không giống như std::set, thực sự sử dụng operator<trực tiếp. Lạ thật ...
Christian Hackl

1
Các chức năng này không sử dụng std::equal_to, chúng sử dụng operator==như đã lưu ý trong câu hỏi. Tình hình với std::lesscũng tương tự. Chà, có lẽ std::setkhông phải là ví dụ tốt nhất.
Lukáš Bednařík

2
@ChristianHackl std::equal_tostd::lessđược sử dụng làm tham số mẫu mặc định trong đó bộ so sánh được lấy làm tham số. operator==operator<được sử dụng trực tiếp trong trường hợp loại được yêu cầu để đáp ứng thứ tự tương đương và thứ tự yếu nghiêm ngặt tương ứng, ví dụ như các trình vòng lặp và các vòng lặp truy cập ngẫu nhiên.
Jan Hudec

28

Đây là mã từ thư viện tiêu chuẩn C ++ loại bỏ mã.

Sai lầm. Đây không phải remove mã thư viện chuẩn C ++ . Đây là một khả năng thực hiện bên trong của removechức năng thư viện chuẩn C ++ . Tiêu chuẩn C ++ không quy định mã thực tế; nó đặt trước các nguyên mẫu chức năng và các hành vi cần thiết.

Nói cách khác: Từ quan điểm ngôn ngữ nghiêm ngặt, mã bạn đang thấy không tồn tại . Nó có thể là từ một số tệp tiêu đề đi kèm với việc triển khai thư viện chuẩn của trình biên dịch của bạn. Lưu ý rằng tiêu chuẩn C ++ thậm chí không yêu cầu các tệp tiêu đề đó tồn tại. Các tệp chỉ là một cách thuận tiện cho người triển khai trình biên dịch để đáp ứng các yêu cầu cho một dòng như #include <algorithm>(nghĩa là tạo std::removevà các chức năng khác có sẵn).

Tại sao bất đẳng thức được kiểm tra if (!(*first == val))thay vì if (*first != val)?

Bởi vì chỉ operator==được yêu cầu bởi chức năng.

Khi nói đến quá tải toán tử cho các loại tùy chỉnh, ngôn ngữ cho phép bạn thực hiện tất cả các loại điều kỳ lạ. Bạn rất có thể tạo ra một lớp có quá tải operator==nhưng không quá tải operator!=. Hoặc thậm chí tệ hơn: Bạn có thể quá tải operator!=nhưng để nó làm những việc hoàn toàn không liên quan.

Xem xét ví dụ này:

#include <algorithm>
#include <vector>

struct Example
{
    int i;

    Example() : i(0) {}

    bool operator==(Example const& other) const
    {
        return i == other.i;
    }

    bool operator!=(Example const& other) const
    {
        return i == 5; // weird, but nothing stops you
                       // from doing so
    }

};

int main()
{
  std::vector<Example> v(10);
  // ...
  auto it = std::remove(v.begin(), v.end(), Example());
  // ...
}

Nếu std::removeđược sử dụng operator!=, thì kết quả sẽ khá khác nhau.


1
Một điều khác cần xem xét là có thể cho cả hai a==ba!=btrả về false. Mặc dù có thể không phải lúc nào cũng rõ ràng liệu tình huống như vậy sẽ được coi là "bằng nhau" hay "không bằng nhau" một cách có ý nghĩa hơn, một hàm xác định đẳng thức chỉ theo toán tử "==" phải coi chúng là "không bằng nhau" ", Bất kể hành vi nào sẽ có ý nghĩa hơn [nếu tôi có máy khoan của mình, tất cả các loại sẽ được mong đợi để tạo ra các toán tử" == "và"! = "hoạt động ổn định, nhưng thực tế là IEEE-754 bắt buộc phải phá vỡ sự bình đẳng điều hành sẽ làm cho kỳ vọng như vậy khó khăn].
supercat

18
"Từ quan điểm ngôn ngữ nghiêm ngặt, mã bạn đang thấy không tồn tại." - khi một quan điểm nói rằng một cái gì đó không tồn tại, nhưng bạn thực sự đang nhìn vào thứ đó, thì quan điểm đó là sai. Trong thực tế, tiêu chuẩn không nói rằng mã không tồn tại, nó chỉ không nói rằng nó tồn tại :-)
Steve Jessop

@SteveJessop: Bạn có thể thay thế biểu thức "từ quan điểm ngôn ngữ nghiêm ngặt" bằng một cái gì đó như "nghiêm túc, ở cấp độ ngôn ngữ". Vấn đề là mã được đăng bởi OP không phải là mối quan tâm của tiêu chuẩn ngôn ngữ.
Christian Hackl

2
@supercat: IEEE-754 thực hiện ==!=hành xử nhất quán, mặc dù tôi luôn nghĩ rằng tất cả sáu mối quan hệ nên đánh giá falsekhi có ít nhất một toán hạng NaN.
Ben Voigt

@BenVoigt: À, đúng rồi. Nó làm cho hai toán tử hành xử theo cùng một kiểu bị hỏng sao cho chúng phù hợp với nhau, nhưng vẫn có thể vi phạm tất cả các tiên đề bình thường khác có liên quan (ví dụ: chúng không duy trì a == a, cũng không đảm bảo rằng các hoạt động được thực hiện trên các giá trị bằng nhau sẽ mang lại kết quả bằng nhau).
supercat

15

Một số câu trả lời tốt ở đây. Tôi chỉ muốn thêm một chút lưu ý.

Giống như tất cả các thư viện tốt, thư viện chuẩn được thiết kế với (ít nhất) hai nguyên tắc rất quan trọng:

  1. Đặt ít trách nhiệm nhất cho người dùng thư viện của bạn mà bạn có thể thoát khỏi. Một phần của điều này có liên quan đến việc cung cấp cho họ ít công việc nhất phải làm khi sử dụng giao diện của bạn. (giống như xác định ít toán tử như bạn có thể thoát khỏi). Phần khác của nó phải làm với việc không làm họ ngạc nhiên hoặc yêu cầu họ kiểm tra mã lỗi (vì vậy hãy giữ giao diện nhất quán và ném ngoại lệ <stdexcept>khi có sự cố).

  2. Loại bỏ tất cả sự dư thừa hợp lý . Tất cả các so sánh có thể được suy luận chỉ từ operator<, vậy tại sao nhu cầu người dùng định nghĩa người khác? ví dụ:

    (a> b) tương đương với (b <a)

    (a> = b) tương đương với! (a <b)

    (a == b) tương đương với! ((a <b) || (b <a))

    và như thế.

    Tất nhiên trên lưu ý này, người ta có thể hỏi tại sao unordered_mapyêu cầu operator==(ít nhất là theo mặc định) chứ không phải operator<. Câu trả lời là trong một bảng băm, sự so sánh duy nhất chúng ta từng yêu cầu là một sự bình đẳng. Do đó, nó phù hợp hơn về mặt logic (nghĩa là có ý nghĩa hơn đối với người dùng thư viện) để yêu cầu họ xác định một toán tử đẳng thức. Yêu cầu một điều operator<sẽ gây nhầm lẫn bởi vì nó không rõ ràng ngay lập tức tại sao bạn cần nó.


10
Về điểm thứ hai của bạn: Có những loại có thể được so sánh một cách hợp lý cho sự bình đẳng ngay cả khi không có thứ tự logic nào tồn tại, hoặc việc thiết lập một trật tự sẽ rất giả tạo. Ví dụ, màu đỏ là màu đỏ và màu đỏ không phải là màu xanh lá cây, mà là màu đỏ vốn ít hơn màu xanh lá cây?
Christian Hackl

1
Hoàn toàn đồng ý. Người ta sẽ không lưu trữ các mặt hàng này trong một container được đặt hàng bởi vì không có thứ tự hợp lý. Chúng có thể được lưu trữ phù hợp hơn trong một container không có thứ tự yêu cầu operator==(và hash).
Richard Hodges

3. Quá tải tối thiểu khai thác tiêu chuẩn càng tốt. Đây là một lý do khác mà họ thực hiện !(a==b). Bởi vì quá tải các toán tử có thể dễ dàng khiến chương trình C ++ bị rối hoàn toàn (cộng với, làm cho lập trình viên phát điên vì việc gỡ lỗi mã của anh ta có thể trở thành nhiệm vụ bất khả thi, vì việc tìm ra thủ phạm của một lỗi cụ thể giống như một cuộc phiêu lưu).
cú pháp

!((a < b) || (b < a))sử dụng một toán tử bool ít hơn, vì vậy nó có thể nhanh hơn
Filip Haglund

1
Trong thực tế, họ không. Trong ngôn ngữ lắp ráp, tất cả các phép so sánh được thực hiện dưới dạng phép trừ sau đó là kiểm tra carry và bit 0 trong thanh ghi cờ. Mọi thứ khác chỉ là cú pháp đường.
Richard Hodges

8

Các EqualityComparablekhái niệm chỉ yêu cầu operator==được xác định.

Do đó, bất kỳ chức năng nào tuyên bố làm việc với các loại thỏa mãn EqualityComparable đều không thể dựa vào sự tồn tại của operator!=các đối tượng thuộc loại đó. (trừ khi có các yêu cầu bổ sung ngụ ý sự tồn tại của operator!=).


1

Cách tiếp cận hứa hẹn nhất là tìm một phương pháp xác định xem toán tử == có thể được gọi cho một loại cụ thể hay không, và sau đó chỉ hỗ trợ nó khi có sẵn; trong các tình huống khác, một ngoại lệ sẽ được ném ra. Tuy nhiên, cho đến nay không có cách nào để phát hiện nếu một biểu thức toán tử tùy ý f == g được xác định phù hợp. Giải pháp tốt nhất được biết có những phẩm chất không mong muốn sau:

  • Thất bại tại thời gian biên dịch cho các đối tượng mà toán tử == không truy cập được (ví dụ: vì nó là riêng tư).
  • Thất bại tại thời gian biên dịch nếu gọi toán tử == là không rõ ràng.
  • Xuất hiện là chính xác nếu toán tử == khai báo là đúng, mặc dù toán tử == có thể không biên dịch.

Từ Boost FAQ: nguồn

Biết rằng yêu cầu ==thực hiện là một gánh nặng , bạn không bao giờ muốn tạo thêm gánh nặng bằng cách yêu cầu!= thực hiện là tốt.

Đối với cá nhân tôi, đó là về phần RẮN (thiết kế hướng đối tượng) Phần L - Nguyên tắc thay thế Liskov: Các đối tượng trong một chương trình nên có thể thay thế bằng các thể hiện của các kiểu con của chúng mà không làm thay đổi tính chính xác của chương trình đó. Trong trường hợp này, nó là toán tử ! = Mà tôi có thể thay thế bằng ==nghịch đảo boolean trong logic boolean.

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.