==와! =는 상호 의존적입니까?


292

나는 C ++에서 연산자 오버로딩에 대해 배우고, 나는 그것을보고 ==!=사용자 정의 형식 사용자 정의 할 수있는 몇 가지 특별한 기능은 간단하다. 그러나 내 관심사는 왜 두 개의 별도 정의가 필요한가? 나는 경우 생각 a == b사실, 다음 a != b, 정의, 때문에, 반대로 자동으로 거짓, 그리고 그 반대이며, 다른 가능성이없는 a != b것입니다 !(a == b). 그리고 이것이 사실이 아닌 상황을 상상할 수 없었습니다. 그러나 아마도 내 상상력이 제한적이거나 무언가에 대해 무지한가?

나는 다른 하나의 관점에서 하나를 정의 할 수 있다는 것을 알고 있지만 이것은 내가 요구하는 것이 아닙니다. 또한 가치 또는 정체성에 의해 객체를 비교하는 것의 차이점에 대해서도 묻지 않습니다. 또는 두 객체가 동시에 같거나 같지 않을 수 있는지 여부 (이것은 분명히 옵션이 아닙니다! 이러한 것들은 상호 배타적입니다). 내가 묻는 것은 이것입니다.

어떤 상황이 수 있는가하는 두 개의 오브젝트가 동일한 메이크업 감각을 수행되고 있지만, 그들에 대한 질문에 대한 질문 요청 하지 이해가되지 않습니다 같다고을? (사용자 관점 또는 구현 자의 관점에서)

그러한 가능성이 없다면, 왜 지구상에서 C ++가 왜이 두 연산자가 두 개의 별개의 함수로 정의되어 있습니까?


13
두 포인터는 모두 null 일 수 있지만 반드시 같을 필요는 없습니다.
Ali Caglayan 2016 년

2
여기서 의미가 있는지 확실하지 않지만 이것을 읽으면 '단락'문제를 생각하게되었습니다. 예를 들어, 'undefined' != expression표현식을 평가할 수 있는지 여부에 관계없이 항상 참 (또는 거짓 또는 정의되지 않음)으로 정의 할 수 있습니다. 이 경우 a!=b정의에 따라 올바른 결과를 반환하지만 평가할 수 없으면 !(a==b)실패합니다 b. (평가 b가 비싼 경우 많은 시간이 걸립니다 ).
Dennis Jaheruddin 2016 년

2
null! = null과 null == null은 어떻습니까? 둘 다 될 수 있습니다. 따라서 a! = b이면 항상 a == b를 의미하지는 않습니다.
zozo

4
javascript의 예(NaN != NaN) == true
chiliNUT

답변:


272

당신은 할 수 없습니다 언어가 자동으로 다시 할 a != b!(a == b)할 때 a == b상 이외 반환 뭔가 bool. 그리고 그렇게하는 데는 몇 가지 이유가 있습니다.

식 작성기 객체가있을 수 있습니다. 여기서는 a == b비교를 수행하지 않으며 의도하지 않지만 단순히를 나타내는 식 노드를 작성 a == b합니다.

a == b직접 비교를 수행하지 않을 목적으로 게으른 평가가있을 수 있지만, 실제로 비교를 수행하기 위해 나중에 암시 적으로 또는 명시 적으로 lazy<bool>변환 할 수있는 종류를 반환합니다 bool. 식 작성기 개체와 결합하여 평가 전에 식을 완전히 최적화 할 수 있습니다.

당신은 몇 가지 사용자 정의 할 수 있습니다 optional<T>옵션 변수를 주어진 템플릿 클래스를 t하고 u, 허용 할 t == u만 반환하게 optional<bool>.

아마 내가 생각하지 못한 것이 더있을 것입니다. 그리고이 예제에서 작업 a == ba != b두 가지 모두 의미가 있지만, 여전히 a != b동일하지 !(a == b)않으므로 별도의 정의가 필요합니다.


72
식 작성은 원하는 경우에 대한 환상적인 실제 예이며, 고려 된 시나리오에 의존하지 않습니다.
Oliver Charlesworth

6
또 다른 좋은 예는 벡터 논리 연산입니다. 그렇다면 !=두 개의 패스 컴퓨팅 대신 데이터 컴퓨팅을 통과하는 것이 좋습니다 . 특히 루프를 통합하기 위해 컴파일러에 의존 할 수 없었던 날에. 또는 오늘날에도 컴파일러를 설득하지 못하면 벡터가 겹치지 않습니다. ==!

41
"당신은 객체 빌더 표현을 가질 수있다"- 그럼 운영자가 !일부 표현 노드를 구축 할 수 있습니다 우리는 여전히 잘 교체하고 a != b함께 !(a == b)지금까지 그 간다. 동일하다 lazy<bool>::operator!, 그것은 반환 할 수 있습니다 lazy<bool>. optional<bool>예를 들어 논리적 인 진실성은 boost::optional가치 자체가 아닌 가치가 존재하는지 여부에 달려 있기 때문에 더 설득력 이 있습니다.
Steve Jessop 2016 년

42
이 모든 Nan것들 과 s-s를 기억하십시오 NaN.
jsbueno 2016 년

9
@jsbueno : NaN이 이와 관련하여 특별하지 않다는 것이 더 지적되었습니다.
Oliver Charlesworth

110

그러한 가능성이 없다면, 왜 지구상에서 C ++가 왜이 두 연산자가 두 개의 별개의 함수로 정의되어 있습니까?

당신은 그것들을 과부하시킬 수 있고, 그것들을 과부하시킴으로써 그들의 원래의 것과 완전히 다른 의미를 줄 수 있습니다.

예를 들어, <<원래 비트 단위의 왼쪽 시프트 연산자 인 operator는 이제 삽입 연산자처럼 일반적으로 오버로드됩니다 std::cout << something. 원래와 완전히 다른 의미입니다.

당신은 당신이 그것을 오버로드 할 때 작업자의 의미가 변화 받아 들일 경우에 따라서, 다음 연산자에 대한 의미 부여에서 사용자를 방지 할 이유가 없다 ==정확하게 아닌 부정 연산자는 !=이 혼동 될 수 있지만.


18
이것은 실제적으로 이해되는 유일한 대답입니다.
Sonic Atom

2
나에게 그것은 당신이 원인과 결과를 거꾸로 가지고있는 것처럼 보입니다. 당신은 개별적으로 과부하로 인해 수 ==!=별개의 연산자로 존재한다. 반면에, 그것들은 개별적으로 오버로드 할 수 있기 때문에 별개의 연산자로 존재하지 않을 수도 있지만 레거시 및 편의 (코드 간결성) 이유 때문입니다.
nitro2k01

60

그러나 내 관심사는 왜 두 개의 별도 정의가 필요한가?

두 가지를 모두 정의 할 필요는 없습니다.
상호 배타적 인 경우 std :: rel_ops를 정의 ==하고 <나란히 정의하면 간결 할 수 있습니다.

안개 cppreference :

#include <iostream>
#include <utility>

struct Foo {
    int n;
};

bool operator==(const Foo& lhs, const Foo& rhs)
{
    return lhs.n == rhs.n;
}

bool operator<(const Foo& lhs, const Foo& rhs)
{
    return lhs.n < rhs.n;
}

int main()
{
    Foo f1 = {1};
    Foo f2 = {2};
    using namespace std::rel_ops;

    //all work as you would expect
    std::cout << "not equal:     : " << (f1 != f2) << '\n';
    std::cout << "greater:       : " << (f1 > f2) << '\n';
    std::cout << "less equal:    : " << (f1 <= f2) << '\n';
    std::cout << "greater equal: : " << (f1 >= f2) << '\n';
}

두 물체가 동등하다는 질문은 이해가되지만, 동일하지 않다는 것은 이해가되지 않는 상황이 있습니까?

우리는 종종 이러한 연산자를 평등에 연결합니다.
이것이 기본 유형에서 작동하는 방식이지만, 이것이 사용자 정의 데이터 유형에서 작동하는 의무는 없습니다. 원하지 않으면 부울을 반환하지 않아도됩니다.

나는 사람들이 연산자를 기괴한 방식으로 과부하시키는 것을 보았지만 도메인 특정 응용 프로그램에 적합하다는 것을 알았습니다. 인터페이스가 상호 배타적임을 나타내는 것처럼 보이지만 저자는 특정 내부 논리를 추가 할 수 있습니다.

(사용자 관점 또는 구현 자의 관점에서)

나는 당신이 특정 예제를 원한다는 것을 알고
있으므로 다음은 내가 생각한 Catch 테스트 프레임 워크의 예 입니다.

template<typename RhsT>
ResultBuilder& operator == ( RhsT const& rhs ) {
    return captureExpression<Internal::IsEqualTo>( rhs );
}

template<typename RhsT>
ResultBuilder& operator != ( RhsT const& rhs ) {
    return captureExpression<Internal::IsNotEqualTo>( rhs );
}

이 연산자는 다른 일을하고 있으므로 한 방법을 다른 방법의! (not)로 정의하는 것은 의미가 없습니다. 이것이 이루어지는 이유는 프레임 워크가 비교를 인쇄 할 수 있기 때문입니다. 그렇게하려면 오버로드 된 연산자가 사용 된 컨텍스트를 캡처해야합니다.


14
오, 어떻게 알지 했어 std::rel_ops? 지적 해 주셔서 감사합니다.
Daniel Jour

5
cppreference (또는 다른 곳)에서 거의 완전 사본을 명확하게 표시하고 올바르게 표시해야합니다. rel_ops어쨌든 끔찍하다.
TC

@TC 동의합니다. 방금 OP가 취할 수있는 방법을 말하는 것입니다. 표시된 예제보다 간단한 rel_ops를 설명하는 방법을 모르겠습니다. 나는 그것이 어디에 있는지 링크했지만 참조 페이지가 항상 변경 될 수 있기 때문에 코드를 게시했습니다.
Trevor Hickey 2016 년

4
여전히 코드 예제가 자신이 아닌 cppreference의 99 %임을 분명히해야합니다.
TC

2
Std :: relops가 유리하지 않은 것 같습니다. 보다 타겟이 분명한 부스트 작전을 확인하십시오.
JDługosz 2016 년

43

거기에 아주 잘 확립 된 규칙은 (a == b)하고 (a != b)있습니다 모두 거짓 반드시 반대하지. 특히, SQL에서 NULL과 비교하면 true 또는 false가 아닌 NULL이 생성됩니다.

직관적이지 않기 때문에 가능한 경우 새로운 예제를 작성하는 것은 좋지 않습니다. 그러나 기존 규칙을 모델링하려는 경우 연산자가 "정확하게"동작하도록하는 옵션을 갖는 것이 좋습니다. 문맥.


4
C ++에서 SQL과 같은 null 동작을 구현하고 있습니까? ewwww. 그러나 나는 그것이 언어로 금지되어야한다고 생각하는 것이 아니라고 생각하지만 불쾌 할 수도 있습니다.

1
@ dan1111 더 중요한 것은 일부 SQL의 풍미가 c ++로 코딩되어있을 수 있으므로 언어는 구문을 지원해야합니까?
Joe

1
내가 틀렸다면 바로 수정하십시오. 여기 위키 백과를 벗어 났지만 SQL에서 NULL 값과 비교하지 않습니다 .False 가 아닌 Unknown을 반환합니다. 그리고 Unknown의 부정은 여전히 ​​Unknown이 아닙니까? 따라서 SQL 논리가 C ++로 코딩 된 경우 NULL == somethingUnknown을 반환 하지 않고 Unknown NULL != something을 반환하고을 !Unknown반환 하려고 합니다 Unknown. 그리고 그 경우 operator!=의 부정으로 이행 하는 operator==것은 여전히 ​​옳습니다.
Benjamin Lindley

1
@Barmar : 좋습니다. 그러면 "SQL NULL이 이런 식으로 작동 합니다 " 라는 문장 이 어떻게 정확합니까? 비교 연산자 구현을 부울 리턴으로 제한하는 경우 이러한 연산자로 SQL 논리를 구현하는 것이 불가능하다는 의미는 아닙니까?
Benjamin Lindley

2
@Barmar : 글쎄요, 그건 요점이 아닙니다. OP는 이미 그 사실을 알고 있거나이 질문이 존재하지 않을 것입니다. 요점은 1) operator==또는 중 하나를 구현하는 operator!=것이나 다른 하나는 구현 하지 않는 것이 합리적 이거나 2) operator!=의 부정 이외의 방식 으로 구현 하는 것이 적절한 예를 제시하는 것이 었습니다 operator==. NULL 값에 대해 SQL 로직을 구현하는 것은 그렇지 않습니다.
Benjamin Lindley

23

나는 당신의 질문의 두 번째 부분, 즉 :

그러한 가능성이 없다면, 왜 지구상에서 C ++가 왜이 두 연산자가 두 개의 별개의 함수로 정의되어 있습니까?

개발자가 두 가지 모두에 과부하를 가할 수있게하는 이유 중 하나는 성능입니다. 당신은 모두를 구현하여 최적화 할 수 있습니다 ==!=. 그때x != y 보다 저렴할 수 있습니다 !(x == y). 일부 컴파일러는 최적화 할 수 있지만 특히 분기가 많은 복잡한 객체가있는 경우에는 그렇지 않을 수 있습니다.

심지어 개발자가 매우 심각하게 법률과 수학적 개념을 하스켈에, 하나는 여전히 과부하 허용 둘 모두 ==/=여기 (볼 수 있듯이, http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html # v : -61--61- ) :

$ ghci
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
λ> :i Eq
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
        -- Defined in `GHC.Classes'

이것은 아마도 미세 최적화로 간주되지만 경우에 따라 보증 될 수 있습니다.


3
SSE (x86 SIMD) 래퍼 클래스가 이에 대한 좋은 예입니다. 이 A의 pcmpeqb명령은,하지만 포장-비교 명령은 생산하지! = 마스크. 따라서 결과를 사용하는 모든 논리를 되돌릴 수 없다면 다른 명령어를 사용하여 반전시켜야합니다. (재미있는 사실 : AMD의 XOP 명령어 세트는 다음과 같이 압축되어 neq있습니다. 너무 나쁜 인텔은 XOP를 채택 / 확장하지 않았습니다. 곧 죽을 ISA 확장에 유용한 명령어가 있습니다.
Peter Cordes

1
우선 SIMD의 핵심은 성능이며 일반적으로 전체 성능에 중요한 루프에서 수동으로 사용하는 것을 귀찮게합니다. 하나의 명령 ( PXOR비교 마스크 결과를 반전시키기 위해 모두를 사용하여)을 타이트한 루프에 저장하면 문제가 될 수 있습니다.
Peter Cordes

오버 헤드가 하나의 논리적 부정 인 경우에는 성능을 신뢰할 수 없습니다 .
건배와 hth. -Alf

컴퓨팅 x == y비용이에 비해 훨씬 더 많은 비용이 드는 경우 하나 이상의 논리적 부정이 될 수 있습니다 x != y. 후자의 컴퓨팅은 분기 예측 등으로 인해 훨씬 ​​저렴할 수 있습니다.
Centril

16

두 물체가 동등하다는 질문은 이해가되지만, 동일하지 않다는 것은 이해가되지 않는 상황이 있습니까? (사용자 관점 또는 구현 자의 관점에서)

그건 의견입니다. 어쩌면 그렇지 않습니다. 그러나 전지구 적이 지 않은 언어 설계자들은 (적어도 그들에게는) 이해할 수있는 상황에 처한 사람들을 제한하지 않기로 결정했습니다.


13

편집에 응답하여;

즉, 어떤 유형이 연산자를 가질 수 ==있지만 !=, 그 반대의 경우 는 그렇지 않은 경우가 있으며, 그렇게하는 것이 언제 합리적입니까?

일반적 으로 아닙니다. 말이되지 않습니다. 평등 및 관계 연산자는 일반적으로 세트로 제공됩니다. 평등이 있다면 불평등도 마찬가지입니다. 보다 작음, 다음보다 큼<= 등 유사한 방식뿐만 아니라 산술 연산자를 적용하고, 또한 일반적으로 천연 논리적 세트 들어온다.

이것은 std::rel_ops 네임 스페이스 . 동일하고 연산자보다 작은 연산자를 구현하는 경우 해당 네임 스페이스를 사용하면 원래 구현 된 연산자로 구현 된 다른 네임 스페이스를 사용할 수 있습니다.

, 하나가 즉시 다른 것을 의미하지 않거나 다른 측면에서 구현할 수없는 조건이나 상황이 있습니까? 그렇습니다 . 의심 할 여지가 거의 없지만, 있습니다. 다시 한번rel_ops고유 한 네임 스페이스 . 따라서 독립적으로 구현할 수 있도록 허용하면 언어를 활용하여 코드의 사용자 나 클라이언트에게 여전히 자연스럽고 직관적 인 방식으로 필요하거나 필요한 의미를 얻을 수 있습니다.

이미 언급 한 게으른 평가는 이에 대한 훌륭한 예입니다. 또 다른 좋은 예는 평등이나 불평등을 의미하지 않는 의미를 부여하는 것입니다. 이와 유사한 예는 비트 시프트 연산자 <<이며 >>스트림 삽입 및 추출에 사용됩니다. 비록 일반적인 분야에서 눈살을 찌푸리게 할 수도 있지만, 일부 영역의 경우에는 의미가있을 수 있습니다.


12

는 IF ==!=연산자는 실제로 것과 같은 방식으로 평등, 의미하지는 않습니다 <<>>스트림 연산자 비트 시프트 의미하는 것은 아닙니다합니다. 기호가 다른 개념을 의미하는 것처럼 취급하면 상호 배타적 일 필요는 없습니다.

평등의 관점에서, 유스 케이스가 객체를 비교할 수없는 것으로 취급하여 모든 비교가 거짓을 리턴하도록 (또는 연산자가 부울을 리턴하지 않는 경우 비교할 수없는 결과 유형) 처리해야하는 경우 이치에 맞을 수 있습니다. 나는 이것이 보증 될 특정 상황을 생각할 수 없지만 그것이 충분히 합리적이라고 볼 수 있습니다.


7

큰 힘으로 책임감있게 또는 최소한 정말 좋은 스타일 가이드를 얻습니다.

==그리고 !=당신이 원하는대로 도대체을 수행하는 오버로드 할 수 있습니다. 축복이자 저주입니다. 을 !=의미하는 보장은 없습니다 !(a==b).


6
enum BoolPlus {
    kFalse = 0,
    kTrue = 1,
    kFileNotFound = -1
}

BoolPlus operator==(File& other);
BoolPlus operator!=(File& other);

이 연산자 오버로드를 정당화 할 수는 없지만 위의 예 operator!=에서는의 "반대" 로 정의 할 수 없습니다 operator==.



1
@ Snowman : Dafang은 좋은 열거 형이라고 말하지 않으며 (열거를 정의하는 좋은 아이디어도 아닙니다) 요점을 설명하는 예제 일뿐입니다. 이 (아마도 잘못된) 연산자 정의를 사용 !=하면 실제로 반대의 의미는 아닙니다 ==.
AlainD

1
@AlainD 내가 게시 한 링크를 클릭 했습니까? 해당 사이트의 목적을 알고 있습니까? 이것을 "유머"라고합니다.

1
@ Snowman : 나는 확실히 ... 죄송합니다. 링크라고 생각하고 아이러니로 의도되었습니다! : o)
AlainD 2016 년

잠깐, 당신은 단항 과부하 ==?
LF

5

결국, 당신이 그 운영자로 확인하는 것은 표현이다 a == b또는 a != b부울 값을 반환 (IS true또는 false). 이 표현식은 비교 후 상호 배타적이지 않고 비교 후 부울 값을 리턴합니다.


4

[..] 왜 두 개의 별도 정의가 필요합니까?

고려해야 할 사항 중 하나는 다른 연산자의 부정을 사용하는 것보다 이러한 연산자 중 하나를보다 효율적으로 구현할 가능성이 있다는 것입니다.

(여기서 제 예제는 끔찍했지만 요점은 여전히 ​​블룸 필터를 생각해보십시오. 예를 들어 세트에 없는 경우 빠른 테스트를 수행 할 수 있지만 테스트중인 경우 시간이 더 오래 걸릴 수 있습니다.)

정의상 [..] a != b!(a == b)입니다.

그리고 그것을 유지하는 것은 프로그래머로서의 책임입니다. 아마도 테스트를 작성하는 것이 좋습니다.


4
!((a == rhs.a) && (b == rhs.b))단락을 어떻게 허용 하지 않습니까? 경우 !(a == rhs.a)다음 것은, (b == rhs.b)평가되지 않습니다.
Benjamin Lindley

그러나 이것은 나쁜 예입니다. 단락은 여기에 마법의 이점을 추가하지 않습니다.
Oliver Charlesworth

@Oliver Charlesworth 혼자는 아니지만 별도의 연산자와 결합하면 다음 ==과 같이됩니다.의 경우 첫 번째 해당 요소가 같지 않으면 비교가 중지됩니다. 그러나의 경우에 !=구현 된 경우 ==모든 해당 요소를 먼저 비교하여 (동일한 경우) 동일하지 않다는 것을 알 수 있습니다 .P 위의 예에서는 첫 번째 같지 않은 쌍을 찾 자마자 비교를 중단합니다. 실제로 좋은 예입니다.
BarbaraKwarc 2016 년

@ BenjaminLindley 사실, 내 예제는 완전 말도 안되었습니다. 불행히도, 다른 현금 지급기를 만들 수 없습니다. 너무 늦었습니다.
Daniel Jour

1
@BarbaraKwarc : !((a == b) && (c == d))(a != b) || (c != d)단락 효율성의 측면에서 동일합니다.
Oliver Charlesworth 2016 년

2

운영자의 동작을 사용자 정의하여 원하는대로 수행 할 수 있습니다.

당신은 물건을 사용자 정의 할 수 있습니다. 예를 들어 클래스를 사용자 정의 할 수 있습니다. 이 클래스의 객체는 특정 속성을 확인하여 비교할 수 있습니다. 이것이 사실임을 알면 전체 객체에서 모든 단일 속성의 모든 단일 비트를 확인하는 대신 최소 사항 만 확인하는 특정 코드를 작성할 수 있습니다.

더 빠르지는 않지만 무언가가 같은 것을 알 수있는 것보다 빠르지 않다는 것을 알 수있는 경우를 상상해보십시오. 물론, 무언가가 똑같거나 다른지 알아 낸 후에는 약간만 뒤집 으면 반대를 알 수 있습니다. 그러나 해당 비트를 뒤집는 것은 추가 작업입니다. 경우에 따라 코드가 많이 다시 실행될 때 한 번의 작업 (다수로 곱함)을 저장하면 전체 속도가 증가 할 수 있습니다. 예를 들어, 메가 픽셀 화면의 픽셀 당 하나의 작업을 저장하면 백만 개의 작업 만 저장 한 것입니다. 초당 60 개의 화면을 곱하면 더 많은 작업이 저장됩니다.

hvd의 답변 은 몇 가지 추가 예를 제공합니다.


2

그렇습니다. 하나는 "동등"을 의미하고 다른 하나는 "비등가"를 의미하며이 용어는 상호 배타적입니다. 이 연산자에 대한 다른 의미는 혼동되므로 반드시 피해야합니다.


모든 경우에 대해 상호 배타적이지 않습니다 . 예를 들어, 두 개의 무한대는 서로 같지 않고 서로 같지 않습니다.
vladon

@vladon은 일반적인 경우에 다른 것을 사용할 수 있습니까? 아니요. 이는 평등하지 않다는 의미입니다. 나머지는 모두 operator == /! =보다는 특별한 기능으로갑니다
oliora 2016 년

@vladon 제발, 일반적인 경우 대신 내 답변의 모든 사례 를 읽으십시오 .
oliora 2016 년

@vladon 수학에서 이것이 가능한 한 C에서 이러한 이유로 a != b동일하지 않은 예를 들어 줄 수 !(a == b)있습니까?
nitro2k01

2

어쩌면 uncomparable 규칙 a != b이었다 허위 하고 a == b있었다 거짓 무 상태 비트있다.

if( !(a == b || a != b) ){
    // Stateless
}

논리 기호를 재 배열하려면! ([A] || [B]) 논리적으로 ([! A] & [! B])가됩니다
Thijser

주의 반환 형식이 operator==()operator!=()필요하지 않습니다 bool, 그들은 당신이 아직 사업자가 여전히 정의 할 수는 그래서 원한다면 무국적자를 포함 열거 수 있습니다 (a != b) == !(a==b).. 보유
lorro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.