많은 C ++ 표준 라이브러리 코드에서 불평등이 (! (a == b))로 테스트되는 이유는 무엇입니까?


142

이것은 C ++ 표준 라이브러리 remove코드의 코드입니다. 불평등은 왜 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

그 이유는 다른 질문 (기본적으로 "아니요, 반드시 그렇지는 않습니다"로 대답 할 수 있음)과 EqualityComparableHurkyl이 그의 답변 에서 언급 한 개념과 관련이 있습니다.
Marco13

답변:


144

이것은 T에 대한 유일한 요구 사항은을 구현하는 것이므로 의미합니다 operator==. T는 요구할 수 operator!=있지만 여기서 일반적인 아이디어는 템플릿 사용자에게 가능한 한 적은 부담을 주어야하며 다른 템플릿은 필요하다는 것 operator==입니다.


13
템플릿 <클래스 T> 인라인 부울 연산자! = <T a, T b> {return! (a == b); }
Joshua

8
컴파일러가 =의 모든 인스턴스를 교환 할 수없는 시나리오가 있습니까? ! (==)? 이것이 컴파일러가 수행하는 기본 동작이 아닌 이유는 무엇입니까?
Aidan Gomez

20
@AidanGomez 더 좋든 나쁘 든 연산자를 오버로드하여 원하는대로 할 수 있습니다. 의미가 있거나 일관성이 없어도됩니다.
닐 커크

10
x != y와 동일하게 정의되지 않았습니다 !(x == y). 이 연산자가 내장 DSL의 구문 분석 트리를 반환하면 어떻게됩니까?
Brice M. Dempsey

7
@Joshua SFINAE를 사용하여 !=지원 되는지 여부를 감지하려고 시도 하면 operator==제대로 작동하지 않습니다 (지원되지 않더라도 true를 잘못 반환 함 ). 또한 일부 사용 !=이 모호해질 수 있습니다.

36

STL의 대부분의 기능은 operator<또는 에서만 작동합니다 operator==. 이를 위해서는 사용자가이 두 연산자 (또는 때로는 적어도 하나)를 구현하기 만하면됩니다. 예를 들어 순서를 관리 하지 않고 std::set사용 operator<(보다 정확하게 std::lessoperator<기본적으로 호출 ) operator>합니다. remove귀하의 예제에서 템플릿도 비슷한 경우입니다 - 그것은 단지 사용 operator==하지 operator!=가 있도록 operator!=정의 할 필요가 없습니다.


2
함수는 operator<직접 사용하지 않고 대신 std::less기본값을 사용 operator<합니다.
Christian Hackl

1
실제로, 표준 알고리즘 함수는 예를 들어 std::set실제로는 operator<직접 사용하지 않는 것 같습니다 . 이상한 ...
Christian Hackl

1
이 함수는을 사용하지 않고 질문에 명시된대로 std::equal_to사용 operator==합니다. 상황 std::less은 비슷합니다. 아마도 std::set가장 좋은 예는 아닙니다.
Lukáš Bednařík

2
@ChristianHackl, std::equal_tostd::less비교기가 매개 변수로 촬영 기본 템플릿 매개 변수로 사용됩니다. operator==operator<형태가 유사한 약한 엄격한 순서를 각각 충족 평등 예에 반복자 랜덤 액세스 반복자 요구되는 경우에 직접 사용된다.
Jan Hudec

28

이 코드는 C ++ 표준 라이브러리에서 코드를 제거합니다.

잘못된. 그것은 아니다 C ++ 표준 라이브러리 코드입니다. 그건 하나의 가능한 내부 구현 은 C ++ 표준 라이브러리의 기능. C ++ 표준은 실제 코드를 규정하지 않습니다. 함수 프로토 타입과 필요한 동작을 예측합니다.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
"엄격한 언어의 관점에서 현재보고있는 코드는 존재하지 않습니다." -어떤 관점은 무언가가 존재하지 않는다고 말하지만 실제로 그 사실을보고 있다면, 그 관점은 틀립니다. 실제로 표준은 코드가 존재하지 않는다고 말하지 않고 단지 존재한다고 말하지 않습니다. :-)
Steve Jessop

@SteveJessop : "엄격한 언어 관점에서"라는 표현을 "엄격하게 언어 수준에서"와 같은 것으로 바꿀 수 있습니다. 요점은 OP가 게시 한 코드가 언어 표준의 문제가 아니라는 것입니다.
Christian Hackl

2
@supercat : IEEE-754은 메이크업을하지 ==그리고 !=난 항상 여섯 개 관계가 평가해야한다고 생각했습니다 있지만, 지속적으로 작동 false적어도 하나의 피연산자 인 경우 NaN.
Ben Voigt

@ BenVoigt : 아, 맞습니다. 두 연산자가 동일한 방식으로 작동하여 서로 일관성을 유지하지만 동등성과 관련된 다른 모든 일반적인 공리를 위반할 수 있습니다 (예 : a == a 또는 운영 수행 보장을지지하지 않습니다) 동일한 값을 사용하면 동일한 결과를 얻을 수 있습니다).
supercat

15

여기에 좋은 답변이 있습니다. 나는 단지 약간의 메모를 추가하고 싶었다.

모든 우수한 라이브러리와 마찬가지로 표준 라이브러리는 두 가지 매우 중요한 원칙을 염두에두고 설계되었습니다.

  1. 도서관 사용자가 맡을 수있는 최소한의 책임을 져야합니다. 이 중 일부는 인터페이스를 사용할 때 최소한의 작업을 수행하는 것과 관련이 있습니다. (당신이 도망 칠 수있는 적은 수의 연산자를 정의하는 것과 같이). 그것의 다른 부분은 그것들을 놀라게하거나 오류 코드를 확인하도록 요구하지 않는 것과 관련이 있습니다 <stdexcept>.

  2. 모든 논리적 중복성을 제거하십시오 . 모든 비교는 단지에서 추론 할 수 있는데 operator<, 왜 사용자가 다른 것을 정의해야합니까? 예 :

    (a> b)는 (b <a)와 같습니다.

    (a> = b)는! (a <b)와 같습니다.

    (a == b)는! ((a <b) || (b <a))와 같습니다.

    등등.

    이 메모 물론, 하나의 이유를 요청할 수도 있습니다 unordered_map필요 operator==보다는 (적어도 기본적으로) operator<. 해시 테이블에서 우리가 요구하는 유일한 비교는 평등을위한 것입니다. 따라서 동등 연산자를 정의하도록 요구하는 것이 논리적으로 일관성이 있습니다 (즉, 라이브러리 사용자에게 더 의미가 있습니다). operator<왜 필요한지 즉시 알 수 없기 때문에을 요구하는 것은 혼란 스러울 것입니다.


10
두 번째 요점과 관련하여 : 논리적 순서가 없거나 논리적 순서가 매우 인위적인 경우에도 동등성을 논리적으로 비교할 수있는 유형이 있습니다. 예를 들어, 빨간색은 빨간색이고 빨간색은 녹색이 아니지만 빨간색은 본질적으로 녹색보다 작습니까?
Christian Hackl

1
완전히 동의하십시오. 논리적 순서가 없기 때문에 이러한 항목을 주문 된 컨테이너 에 저장하지 않습니다 . 그것들은 operator==(및 hash) 을 요구하는 비 순차 컨테이너에 더 적합하게 저장 될 수 있습니다 .
Richard Hodges

3. 과부하 적어도 가능한 표준 운영. 이것이 그들이 구현 한 또 다른 이유입니다 !(a==b). 연산자의 오버로드가 오버로드되면 C ++ 프로그램이 쉽게 엉망이 될 수 있습니다 ( 또한 특정 버그 의 원인 을 찾는 것이 오디세이와 유사 하기 때문에 코드 디버깅이 미션이 될 수 없기 때문에 프로그래머가 미쳐 버릴 수 있습니다 ).
syntaxerror

!((a < b) || (b < a))덜 bool 연산자를 사용하므로 속도가 더 빠를 수 있습니다
Filip Haglund

1
실제로 그들은 그렇지 않습니다. 어셈블리 언어에서 모든 비교는 빼기로 구현되고 플래그 레지스터에서 캐리 및 0 비트를 테스트합니다. 다른 모든 것은 단지 구문 설탕입니다.
Richard Hodges

8

EqualityComparable개념은 단지 것을 요구 operator==정의 될 수있다.

결과적으로 만족스러운 유형으로 작업한다고 공언하는 기능 은 해당 유형의 객체 에 대한 존재에 의존 EqualityComparable 할 수 없습니다operator!= . (의 존재를 암시하는 추가 요구 사항이없는 한 operator!=).


1

가장 유망한 접근법은 operator ==가 특정 유형에 대해 호출 될 수 있는지 판별 한 후 사용 가능한 경우에만 지원하는 방법을 찾는 것입니다. 다른 상황에서는 예외가 발생합니다. 그러나 현재까지 임의의 연산자 식 f == g가 적절히 정의되어 있는지 여부를 감지하는 알려진 방법은 없습니다. 알려진 최상의 솔루션은 다음과 같은 바람직하지 않은 품질입니다.

  • operator ==에 액세스 할 수없는 개체 (예 : 개인용 개체)에 대해 컴파일 타임에 실패합니다.
  • operator == 호출이 모호한 경우 컴파일 타임에 실패합니다.
  • operator ==가 컴파일되지 않더라도 operator == 선언이 올바른 경우 올바른 것으로 나타납니다.

Boost FAQ에서 : 출처

==구현 을 요구 하는 것이 부담 임을 알면 구현 을 요구 하여 추가 부담을 일으키고 싶지 않습니다 !=.

저에게는 개인적으로 SOLID (객체 지향 설계) L 부분-Liskov 대체 원칙에 관한 것입니다. 이 경우 부울 논리에서 ==부울 역 으로 바꿀 수 있는 것은 연산자 ! = 입니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.