ทำไมการทดสอบความไม่เท่าเทียมกันเป็น (! (a == b)) ในรหัสไลบรารีมาตรฐาน C ++ จำนวนมาก


142

นี่คือรหัสจากremoveรหัสไลบรารีมาตรฐาน C ++ ทำไมไม่เท่าเทียมกันทดสอบเป็นif (!(*first == val))แทน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 อาจถูกต้อง operator!=นอกจากนี้ยังเป็นที่พบบ่อยเมื่อใช้ เพียงใช้การoperator==ดำเนินการ:bool operator!=(const Foo& other) { return !(*this == other); }
simon

1
จริง ๆ แล้วฉันแก้ไขข้อความของฉัน: การอ้างอิงถึงการลบองค์ประกอบทั้งหมดที่มีค่าดังนั้นจึงoperator==คาดว่าจะใช้ที่นี่ ...
BeyelerStudios

โอ้และควรมีconstตัวอย่างในความคิดเห็นก่อนหน้าของฉันด้วย แต่คุณจะได้รับประเด็น (สายเกินกว่าจะแก้ไขได้)
simon

เหตุผลของเรื่องนี้มีความเกี่ยวข้องกับคำถามอื่น (ที่สามารถตอบโดยทั่วไปด้วย "ไม่มีไม่จำเป็นต้อง") และแนวคิดของการเป็นEqualityComparableที่ Hurkyl กล่าวถึงในคำตอบของเขา
Marco13

คำตอบ:


144

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


13
แม่แบบ <class T> ตัวดำเนินการแบบอินไลน์บูล! = <T a, T b> {return! (a == b); }
โยชูวา

8
จะมีสถานการณ์ใด ๆ ที่คอมไพเลอร์ไม่สามารถแลกเปลี่ยนอินสแตนซ์ทั้งหมดของ =! ถึง! (==)? เหตุใดจึงไม่เป็นการกระทำเริ่มต้นสำหรับคอมไพเลอร์ที่จะใช้?
Aidan Gomez

20
@AidanGomez ให้ดีขึ้นหรือแย่ลงคุณสามารถโอเวอร์โหลดโอเปอร์เรเตอร์เพื่อทำสิ่งที่คุณต้องการ ไม่จำเป็นต้องมีเหตุผลหรือไม่สอดคล้องกัน
Neil Kirk

10
x != y!(x == y)ไม่ได้กำหนดที่จะเป็นเช่นเดียวกับ จะเกิดอะไรขึ้นถ้าโอเปอเรเตอร์เหล่านี้ส่งคืนทรีการแยกวิเคราะห์ของ DSL ที่ฝังอยู่
Brice M. Dempsey

7
@Joshua ที่แบ่งไม่ดีถ้าพยายามที่จะใช้ SFINAE การตรวจสอบว่า!=ได้รับการสนับสนุน (จะไม่ถูกต้องกลับจริง - แม้ว่าoperator==จะไม่ได้รับการสนับสนุน!) ฉันยังกังวลว่าจะทำให้การใช้งานบางอย่าง!=กลายเป็นคลุมเครือ

36

ฟังก์ชั่นส่วนใหญ่ในการทำงานเฉพาะกับ STL หรือoperator< operator==สิ่งนี้ต้องการให้ผู้ใช้ดำเนินการตัวดำเนินการสองตัวนี้ (หรือบางครั้งก็อย่างน้อยหนึ่งตัว) ตัวอย่างเช่นstd::setใช้operator<(แม่นยำยิ่งขึ้นstd::lessซึ่งเรียกoperator<ตามค่าเริ่มต้น) และไม่operator>จัดการสั่งซื้อ removeแม่แบบในตัวอย่างของคุณเป็นกรณีคล้ายกัน - มันใช้เพียงoperator==และไม่ได้operator!=ดังนั้นoperator!=ไม่จำเป็นต้องมีการกำหนดไว้


2
ฟังก์ชั่นไม่ได้ใช้operator<โดยตรง แต่แทนที่จะใช้ซึ่งในค่าเริ่มต้นหันไปstd::less operator<
Christian Hackl

1
ที่จริงแล้วดูเหมือนว่าฟังก์ชั่นอัลกอริทึมมาตรฐานซึ่งแตกต่างจากเช่นstd::setใช้operator<โดยตรงจริง ๆ แปลก ...
คริสเตียนแฮ็

1
ฟังก์ชั่นเหล่านี้ไม่ได้ใช้std::equal_toแต่ใช้operator==ตามที่ระบุไว้ในคำถาม สถานการณ์ที่std::lessคล้ายกัน บางทีอาจstd::setไม่ใช่ตัวอย่างที่ดีที่สุด
LukášBednařík

2
@ChristianHackl std::equal_toและstd::lessใช้เป็นพารามิเตอร์เทมเพลตเริ่มต้นที่มีการเปรียบเทียบเป็นพารามิเตอร์ operator==และoperator<มีการใช้โดยตรงในกรณีที่ต้องการประเภทเพื่อตอบสนองความเท่าเทียมกันและการสั่งซื้อที่อ่อนแออย่างเข้มงวดตามลำดับเช่นตัววนซ้ำและตัววนซ้ำการเข้าถึงแบบสุ่ม
Jan Hudec

28

นี่คือรหัสจากรหัสลบไลบรารีมาตรฐาน C ++

ไม่ถูกต้อง. มันไม่ได้เป็นc ++ มาตรฐานห้องสมุดรหัส มันเป็นหนึ่งในการนำไปใช้ภายในของฟังก์ชันไลบรารีมาตรฐาน C ++ มาตรฐาน C ++ ไม่ได้กำหนดรหัสจริง มัน prescibes ฟังก์ชั่นต้นแบบและพฤติกรรมที่ต้องการremoveremove

ในคำอื่น ๆ : จากจุดภาษาเข้มงวดในมุมมองของรหัสที่คุณจะได้เห็นไม่ได้อยู่ อาจมาจากไฟล์ส่วนหัวบางไฟล์ที่มาพร้อมกับการใช้งานไลบรารีมาตรฐานของคอมไพเลอร์ของคุณ โปรดทราบว่ามาตรฐาน C ++ ไม่จำเป็นต้องมีไฟล์ส่วนหัวเหล่านั้นอยู่ ไฟล์เป็นเพียงวิธีที่สะดวกสำหรับผู้ดำเนินการรวบรวมเพื่อตอบสนองความต้องการสำหรับบรรทัดเช่น#include <algorithm>(เช่นการทำstd::removeและฟังก์ชั่นอื่น ๆ ที่มี)

ทำไมไม่เท่าเทียมกันทดสอบเป็นif (!(*first == val))แทนif (*first != val)?

เพราะoperator==ฟังก์ชั่นที่ต้องการเท่านั้น

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

ลองพิจารณาตัวอย่างนี้:

#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());
  // ...
}

ถ้าstd::removeใช้operator!=แล้วผลจะแตกต่างกันมาก


1
สิ่งที่ต้องพิจารณาอีกประการหนึ่งคืออาจเป็นไปได้สำหรับทั้งคู่a==bและa!=bเพื่อส่งคืนเท็จ ในขณะที่มันอาจไม่ชัดเจนว่าสถานการณ์ดังกล่าวจะได้รับการพิจารณาอย่างมีความหมายมากขึ้นว่า "เท่ากัน" หรือ "ไม่เท่ากัน" ฟังก์ชันที่กำหนดความเสมอภาคเพียงอย่างเดียวในแง่ของโอเปอเรเตอร์ "==" ต้องถือว่าพวกเขาเป็น "ไม่ว่าพฤติกรรมแบบใดจะเหมาะสมกว่า [ถ้าฉันมีคนชำนาญการทุกประเภทจะคาดหวังว่าจะให้ผลบูลีน" == "และ"! = "ตัวดำเนินการทำงานอย่างสม่ำเสมอ แต่ความจริงที่ว่า IEEE-754 ผู้ประกอบการจะทำให้ความคาดหวังดังกล่าวเป็นเรื่องยาก]
supercat

18
"จากมุมมองภาษาที่เข้มงวดไม่มีรหัสที่คุณเห็น" - เมื่อมุมมองบอกว่ามีบางสิ่งไม่อยู่ แต่จริง ๆ แล้วคุณกำลังมองสิ่งนั้นอยู่มุมมองนั้นผิด ในความเป็นจริงมาตรฐานไม่ได้บอกว่ารหัสไม่อยู่ก็แค่ไม่ได้บอกว่ามันไม่อยู่ :-)
สตีฟเจสซอพ

@SteveJessop: คุณสามารถแทนที่นิพจน์ "จากมุมมองภาษาที่เข้มงวด" ด้วยบางสิ่งเช่น "เคร่งครัดในระดับภาษา" ประเด็นก็คือรหัสที่โพสต์โดย OP ไม่ใช่ข้อกังวลของมาตรฐานภาษา
Christian Hackl

2
@supercat: IEEE-754 จะทำให้==และ!=ประพฤติอย่างต่อเนื่องแม้ว่าฉันคิดเสมอว่าทุกหกควรประเมินความสัมพันธ์ไปเมื่ออย่างน้อยหนึ่งตัวถูกดำเนินการคือfalse NaN
Ben Voigt

@BenVoigt: อ่าใช่แล้ว มันทำให้ผู้ปฏิบัติงานทั้งสองมีพฤติกรรมที่แตกสลายเหมือนกันซึ่งพวกเขาสอดคล้องกัน แต่ก็ยังสามารถละเมิดหลักการทั่วไปอื่น ๆ ทั้งหมดที่เกี่ยวข้องกับความเท่าเทียมกัน (เช่นพวกเขาสนับสนุนทั้ง == a หรือไม่รับประกันว่าการดำเนินการจะดำเนินการ ค่าที่เท่ากันจะให้ผลลัพธ์ที่เท่ากัน)
supercat

15

คำตอบที่ดีที่นี่ ฉันแค่อยากจะเพิ่มโน้ตเล็กน้อย

เช่นเดียวกับห้องสมุดที่ดีห้องสมุดมาตรฐานได้รับการออกแบบโดยมีหลักการสำคัญสองประการ (อย่างน้อย):

  1. ให้ความรับผิดชอบต่อผู้ใช้ห้องสมุดน้อยที่สุดเท่าที่จะเป็นไปได้ ส่วนหนึ่งของสิ่งนี้เกี่ยวข้องกับการให้งานน้อยที่สุดเมื่อใช้อินเทอร์เฟซของคุณ (เช่นกำหนดผู้ประกอบการน้อยตามที่คุณสามารถไปด้วย) ส่วนอื่น ๆ นั้นเกี่ยวข้องกับพวกเขาไม่น่าแปลกใจหรือต้องการให้พวกเขาตรวจสอบรหัสข้อผิดพลาด (เพื่อให้อินเตอร์เฟซที่สอดคล้องกันและโยนข้อยกเว้นจาก<stdexcept>เมื่อสิ่งผิดปกติ)

  2. กำจัดทุกความซ้ำซ้อนตรรกะ การเปรียบเทียบทั้งหมดสามารถอนุมานได้เพียงจากoperator<เหตุใดจึงต้องการให้ผู้ใช้กำหนดผู้อื่น เช่น:

    (a> b) เทียบเท่ากับ (b <a)

    (a> = b) เทียบเท่ากับ! (a <b)

    (a == b) เทียบเท่ากับ! ((<<) || (b <a))

    และอื่น ๆ

    แน่นอนในบันทึกนี้หนึ่งอาจจะถามว่าทำไมunordered_mapต้องใช้operator==(อย่างน้อยโดยค่าเริ่มต้น) operator<มากกว่า คำตอบก็คือในตารางแฮชการเปรียบเทียบสิ่งเดียวที่เราต้องการคือความเท่าเทียมกัน ดังนั้นจึงเป็นเหตุผลที่สอดคล้องกันมากขึ้น(เช่นทำให้ผู้ใช้ห้องสมุดมีเหตุผลมากขึ้น) เพื่อกำหนดให้ผู้ดำเนินการความเท่าเทียมกัน การoperator<ขอจะทำให้สับสนเพราะมันไม่ชัดเจนในทันทีว่าทำไมคุณถึงต้องการมัน


10
เกี่ยวกับประเด็นที่สองของคุณ: มีหลายประเภทที่สามารถเปรียบเทียบได้ในเชิงตรรกะเพื่อความเท่าเทียมกันแม้ว่าจะไม่มีคำสั่งทางตรรกะอยู่หรือการสร้างคำสั่งซื้อนั้นเป็นสิ่งประดิษฐ์มาก ตัวอย่างเช่นสีแดงคือสีแดงและสีแดงไม่ใช่สีเขียว แต่สีแดงโดยธรรมชาติน้อยกว่าสีเขียวใช่หรือไม่
Christian Hackl

1
เห็นด้วยอย่างสมบูรณ์ หนึ่งจะไม่เก็บรายการเหล่านี้ในภาชนะสั่งซื้อเพราะไม่มีคำสั่งตรรกะ อาจถูกจัดเก็บในภาชนะที่ไม่ได้เรียงลำดับซึ่งต้องการoperator==(และhash) อย่างเหมาะสมยิ่งขึ้น
Richard Hodges

3.โอเวอร์โหลดตัวดำเนินการมาตรฐานที่น้อยที่สุดเท่าที่จะทำได้ !(a==b)นี่คือเหตุผลที่พวกเขานำมาใช้อีก เพราะการบรรทุกเกินพิกัด unreflected ของผู้ประกอบการได้อย่างง่ายดายอาจทำให้เกิด c ++ โปรแกรมที่จะได้รับ messed ขึ้น (บวกให้โปรแกรมเมอร์ไปบ้าเพราะการแก้จุดบกพร่องรหัสของเขาอาจจะกลายเป็นภารกิจที่เป็นไปไม่ได้เนื่องจากการหาผู้กระทำผิดของข้อผิดพลาดโดยเฉพาะอย่างยิ่งคล้าย Odyssey)
ไวยากรณ์

!((a < b) || (b < a))ใช้โอเปอเรเตอร์บูลที่น้อยกว่าดังนั้นจึงน่าจะเร็วกว่า
Filip Haglund

1
ในความเป็นจริงพวกเขาทำไม่ได้ ในภาษาแอสเซมบลีการเปรียบเทียบทั้งหมดจะถูกนำไปใช้เป็นลบตามด้วยการทดสอบการถือและศูนย์บิตในการลงทะเบียนธง ทุกอย่างอื่นเป็นเพียงน้ำตาลประโยค
Richard Hodges

8

EqualityComparableแนวคิดเพียงต้องการให้operator==มีการกำหนดไว้

ดังนั้นฟังก์ชั่นใด ๆ ที่ยอมรับการทำงานกับประเภทที่น่าพอใจEqualityComparable ไม่สามารถพึ่งพาการดำรงอยู่ของoperator!=วัตถุประเภทนั้น (เว้นแต่มีข้อกำหนดเพิ่มเติมที่บ่งบอกถึงการมีอยู่ของoperator!=)


1

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

  • ล้มเหลวในเวลารวบรวมสำหรับวัตถุที่โอเปอเรเตอร์ == ไม่สามารถเข้าถึงได้ (เช่นเนื่องจากเป็นส่วนตัว)
  • ล้มเหลวในเวลารวบรวมหากผู้ดำเนินการเรียก == ไม่ชัดเจน
  • ดูเหมือนว่าจะถูกต้องหากผู้ดำเนินการ == ประกาศถูกต้องแม้ว่าผู้ประกอบการ == อาจไม่ได้รวบรวม

จากคำถามที่พบบ่อย Boost: แหล่งที่มา

การรู้ว่าต้องมี==การนำไปปฏิบัติเป็นภาระคุณไม่ต้องการสร้างภาระเพิ่มเติมโดยต้องมี!=การนำไปปฏิบัติเช่นกัน

สำหรับฉันเป็นการส่วนตัวเกี่ยวกับSOLID (การออกแบบเชิงวัตถุ) ส่วน L - หลักการทดแทน Liskov:“ วัตถุในโปรแกรมควรเปลี่ยนได้ด้วยอินสแตนซ์ของชนิดย่อยโดยไม่ต้องแก้ไขความถูกต้องของโปรแกรมนั้น” ในกรณีนี้มันคือโอเปอเรเตอร์! =ที่ฉันสามารถแทนที่ด้วย==และบูลีนผกผันในตรรกะบูลีน

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