C ++ 컴파일러가 operator == 및 operator! =를 정의하지 않는 이유는 무엇입니까?


302

나는 컴파일러가 가능한 한 많은 작업을 수행하게하는 것을 좋아합니다. 간단한 클래스를 작성할 때 컴파일러는 '무료'에 대해 다음을 제공 할 수 있습니다.

  • 기본 (빈) 생성자
  • 복사 생성자
  • 소멸자
  • 할당 연산자 ( operator=)

같은 -하지만 당신에게 비교 연산자 줄 수없는 것 operator==또는 operator!=. 예를 들면 다음과 같습니다.

class foo
{
public:
    std::string str_;
    int n_;
};

foo f1;        // Works
foo f2(f1);    // Works
foo f3;
f3 = f2;       // Works

if (f3 == f2)  // Fails
{ }

if (f3 != f2)  // Fails
{ }

이것에 대한 정당한 이유가 있습니까? 멤버 별 비교를 수행하는 것이 왜 문제가됩니까? 분명히 클래스가 메모리를 할당하면 조심하고 싶지만 간단한 클래스의 경우 컴파일러 가이 작업을 수행 할 수 있습니까?


4
물론 소멸자도 무료로 제공됩니다.
Johann Gerell

23
그의 최근 대화 중 하나에서 Alex Stepanov는 특정 조건에서 ==기본 자동 할당 ( =) 이있는 것과 같은 방식 으로 기본 자동을 갖지 않는 것은 실수라고 지적했습니다 . (논리가 모두 적용되기 때문에 포인터에 대한 인수는 일관성 ===, 그리고뿐만 아니라 초).
alfC

2
@becko A9의 시리즈 중 하나입니다 : youtube.com/watch?v=k-meLQaYP5Y , 나는 어느 대화에서 기억이 나지 않습니다. C ++ 17 open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0221r0.html
alfC

1
@becko는 Youtube에서 사용할 수있는 A9의 "컴포넌트를 사용한 효율적인 프로그래밍"또는 "프로그래밍 대화"시리즈 중 첫 번째 중 하나입니다.
alfC

1
@becko 실제로 Alex의 관점을 가리키는 대답은 stackoverflow.com/a/23329089/225186
alfC

답변:


71

컴파일러는 포인터 비교 또는 심층 (내부) 비교를 원하는지 알 수 없습니다.

구현하지 않고 프로그래머가 직접 수행하는 것이 더 안전합니다. 그런 다음 원하는 모든 가정을 할 수 있습니다.


292
이 문제로 인해 복제 ctor가 생성되는 것을 막을 수는 없습니다.
MSalters

78
복사 생성자 (및 operator=)는 일반적으로 비교 연산자와 동일한 컨텍스트에서 작동 합니다. 즉 a = b, 을 수행 한 후에 a == b는 참 이 될 것으로 예상 됩니다. 컴파일러 operator==와 동일한 집계 값 의미론을 사용하여 기본값을 제공하는 것이 operator=좋습니다. 나는 paercebal이 실제로 operator=C 호환성을 위해서만 제공되고 상황을 악화시키고 싶지 않다는 점에서 실제로 정확하다고 생각 합니다.
Pavel Minaev

46
-1. 물론 당신은 깊은 비교를 원합니다. 만약 프로그래머가 포인터 비교를 원한다면, 그는 (& f1 == & f2)
Viktor Sehr

62
빅토르, 나는 당신이 당신의 응답을 다시 생각하는 것이 좋습니다. Foo 클래스에 Bar *가 포함되어 있으면 컴파일러는 Foo :: operator ==가 Bar *의 주소 또는 Bar의 내용을 비교할 것인지 어떻게 알 수 있습니까?
Mark Ingram

46
@Mark : 포인터를 포함하는 경우 포인터 값을 비교하는 것이 합리적입니다. 값을 포함하는 경우 값을 비교하는 것이 합리적입니다. 예외적 인 상황에서 프로그래머는 무시할 수 있습니다. 이것은 언어가 int와 pointer-to-int를 비교하는 것과 같습니다.
Eamon Nerbonne

317

컴파일러가 기본 복사 생성자를 제공 할 수 있다면 비슷한 기본값을 제공 할 수 있어야한다는 주장 operator==()은 어느 정도 의미가 있습니다. 이 연산자에 대해 컴파일러에서 생성 한 기본값을 제공하지 않기로 한 이유는 Stroustrup이 "C ++의 디자인 및 진화"(11.4.1 장-복사 제어)의 기본 복사 생성자에 대해 말한 것으로 추측 할 수 있다고 생각합니다. :

필자는 개인적으로 복사 작업이 기본적으로 정의되어 많은 클래스의 객체를 복사하는 것을 금지하는 것이 불행하다고 생각합니다. 그러나 C ++은 기본 할당 및 C에서 복사 생성자를 상속했으며 자주 사용됩니다.

따라서 "C ++에 기본값이없는 이유는 operator==()무엇입니까?" 대신 " C ++에 기본 할당 및 사본 생성자가있는 이유는 무엇입니까?"라는 질문에 대한 답이되어야합니다. (아마도 C ++의 사마귀의 원인 일뿐 만 아니라 C ++의 인기에 대한 주된 이유 일 수도 있습니다).

내 자신의 목적을 위해 내 IDE에서 새 클래스에 사용하는 스 니펫에는 개인 할당 연산자 및 복사 생성자에 대한 선언이 포함되어 있으므로 새 클래스를 생성 할 때 기본 할당 및 복사 작업을하지 않습니다-선언을 명시 적으로 제거해야합니다 private:컴파일러가 나를 위해 생성 할 수 있기를 원한다면 섹션 에서 해당 작업 중 하나를 수행하십시오.


29
좋은 대답입니다. 할당 연산자와 복사 생성자를 비공개로 만드는 대신 C ++ 11에서 다음 Foo(const Foo&) = delete; // no copy constructor과 같이 완전히 제거 할 수 있습니다. Foo& Foo=(const Foo&) = delete; // no assignment operator
karadoc

9
"그러나 C ++은 기본 할당과 C에서 복사 생성자를 상속했습니다."이 방법으로 모든 C ++ 유형을 만들어야하는 것은 아닙니다. 그들은 이것을 기존의 POD로 제한 했어야했는데, 이미 C에있는 유형은 더 이상 없습니다.
thesaint

3
C ++이 왜 이러한 동작을 상속했는지 이해할 수는 struct있지만 class다르게 동작 하는 것이 바람직합니다. 이 과정에서 또한 기본 액세스 struct와 그 class옆에 더 의미있는 차이가 생겼을 것입니다.
jamesdlin

@jamesdlin 규칙을 원한다면 ctor의 암시 적 선언과 정의 및 dtor가 선언 된 경우 할당을 비활성화하는 것이 가장 적합합니다.
중복 제거기

1
프로그래머가 명시 적으로 컴파일러에게을 만들도록 명령하는 데 아무런 해를 끼치 지 않습니다 operator==. 이 시점에서 일부 보일러 플레이트 코드의 구문 설탕입니다. 이 방법으로 프로그래머가 클래스 필드 사이에서 일부 포인터를 간과 할 수 있다고 생각되면 등호 연산자가있는 기본 유형 및 오브젝트에서만 작동 할 수있는 조건을 추가 할 수 있습니다. 그러나 이것을 완전히 거부 할 이유는 없습니다.
NO_NAME

93

심지어 C ++ 20, 컴파일러는 여전히 암시 적으로 생성하지 않습니다 operator==당신을 위해

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

그러나 C ++ 20 이후 명시 적으로 기본값 을 지정할 수 있습니다 .==

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

디폴트은 ==회원이 많다는 않습니다 ==(기본 복사 생성자는 멤버 현명한 복사 건설을 수행하는 것과 같은 방식으로). 새로운 규칙은 사이 예상되는 관계를 제공 ==하고 !=. 예를 들어 위의 선언으로 두 가지를 모두 쓸 수 있습니다.

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

이 특정 기능 (디폴트 operator==와 대칭성 ==!=)에서 유래 한 제안 입니다 광범위한 언어 기능의 일부였다 operator<=>.


이것에 대한 최신 업데이트가 있는지 알고 있습니까? C ++ 17에서 사용할 수 있습니까?
dcmm88

3
@ dcmm88 어쨌든 C ++ 17에서는 사용할 수 없습니다. 답변을 업데이트했습니다.
안톤 사빈

2
똑같은 것을 허용하는 수정 된 제안 (짧은 형식은 제외)은 C ++ 20이지만 :)
Rakete1111

기본적으로 당신은 기본적으로 = default생성되지 않은 것을 지정해야합니다 . 그것은 oxymoron처럼 들립니다 ( "명시적인 기본값").
artin December

@artin 언어에 새로운 기능을 추가하면 기존 구현이 중단되지 않아야합니다. 새로운 라이브러리 표준이나 컴파일러가 할 수있는 새로운 일을 추가하는 것은 한 가지 일입니다. 이전에 존재하지 않았던 새로운 멤버 함수를 추가하는 것은 완전히 다른 이야기입니다. 실수로부터 프로젝트를 보호하려면 훨씬 더 많은 노력이 필요합니다. 개인적으로 명시 적 및 암시 적 기본값 사이를 전환하기 위해 컴파일러 플래그를 선호합니다. 이전 C ++ 표준에서 프로젝트를 빌드하고 컴파일러 플래그로 명시 적 기본값을 사용하십시오. 이미 컴파일러를 업데이트 했으므로 올바르게 구성해야합니다. 새 프로젝트의 경우 암시 적으로 만듭니다.
Maciej Załucki

44

IMHO, "좋은"이유는 없습니다. 이 디자인 결정에 동의하는 사람들이 너무 많은 이유는 가치 기반 시맨틱의 힘을 마스터하는 법을 배우지 않았기 때문입니다. 사람들은 구현에 원시 포인터를 사용하기 때문에 많은 사용자 정의 복사 생성자, 비교 연산자 및 소멸자를 작성해야합니다.

적절한 스마트 포인터 (예 : std :: shared_ptr)를 사용하는 경우 기본 복사 생성자는 일반적으로 훌륭하며 가상의 기본 비교 연산자의 명백한 구현은 훌륭합니다.


39

C ++은 C 가하지 않았기 때문에 ==를하지 않았다고 대답했습니다 .C는 기본적으로 = =를 제공하지 않는 이유는 무엇입니까? C는 간단하게 유지하고 싶었다. C 구현 = memcpy; 그러나 패딩으로 인해 memcmp로 ==을 구현할 수 없습니다. 패딩이 초기화되지 않았기 때문에 memcmp는 동일하더라도 서로 다르다고 말합니다. 빈 클래스에도 같은 문제가 있습니다. memcmp는 빈 클래스의 크기가 0이 아니기 때문에 서로 다르다고 말합니다. 그것은 == 구현하는 것은 더 C에서 일부 코드 = 구현을보다 복잡하게되는 것을 위에서 볼 수있는 예를 들어 ,이에 대한합니다. 내가 틀렸다면 정정 해 주셔서 감사합니다.


6
C ++은 memcpy를 사용하지 않습니다 operator=-POD 유형에서만 작동하지만 C ++은 operator=비 POD 유형에도 기본값 을 제공합니다 .
Flexo

2
예, C ++는보다 정교한 방식으로 구현했습니다. C가 단순한 memcpy로 구현 된 것 같습니다.
Rio Wing

이 답변의 내용은 Michael과 함께해야합니다. 그의 질문을 수정 한 다음 대답합니다.
Sgene9

27

비디오 에서 STL을 만든 Alex Stepanov는이 문제를 약 13:00에 해결합니다. 요약하자면, C ++의 진화를 지켜 보면서 그는 다음과 같이 주장합니다.

  • 불행히도 ==와! = 는 암시 적으로 선언되지 않습니다 (그리고 Bjarne은 그에게 동의합니다). 올바른 언어는 그러한 것들을 준비해야합니다 (그는 계속 해서 의미를 깨는 ! = 를 정의 할 수 없어야한다고 제안합니다 == )
  • 이것이 C의 근본 원인 (많은 C ++ 문제) 인 이유는 할당 연산자가 비트 단위 할당으로 암시 적으로 정의되어 있지만 == 에서는 작동하지 않습니다 . 이 기사 에서 Bjarne Stroustrup의 자세한 설명을 볼 수 있습니다 .
  • 후속 질문에서 왜 회원 비교에 의해 회원이 아니 었습니까? 그는 놀라운 것을 말합니다 .

그런 다음 (먼) 미래에 ==! = 가 암시 적으로 생성 될 것이라고 말합니다 .


2
이 먼 미래가 2017 년이나 18 년, 19 년이되지 않을 것 같습니다. 당신은 내 드리프트를 잡을 것입니다 ...
UmNyobe

18

C ++ 20은 기본 비교 연산자를 쉽게 구현할 수있는 방법을 제공합니다.

cppreference.com의 예 :

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>

4
두 점을 사용 하여 좌표 를 주문하는 합리적인 기본 방법이 없기 때문에 주문 작업 Point의 예로 사용 된 것에 놀랐습니다 .xy
pipe

4
@pipe 요소의 순서를 신경 쓰지 않으면 기본 연산자를 사용하는 것이 좋습니다. 예를 들어, std::set모든 포인트가 고유하고 std::set사용 만하는 데 사용할 operator<수 있습니다.
vll

반환 유형에 대한 auto관계 : 이 경우 우리는 항상있을 것입니다 가정 할 수 있습니다 std::strong_ordering에서 #include <compare>?
kevinarpe

1
@kevinarpe 반환 유형은 std::common_comparison_category_t이 클래스의 기본 순서 ( std::strong_ordering)가됩니다.
vll

15

default를 정의 ==할 수는 없지만 일반적으로 스스로 정의해야하는 기본값 !=을 정의 할 수 있습니다 ==. 이를 위해 다음과 같은 작업을 수행해야합니다.

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

자세한 내용 은 http://www.cplusplus.com/reference/std/utility/rel_ops/ 를 참조하십시오.

또한을 정의하면을 operator< 사용할 때 <=,>,> =에 대한 연산자를 추론 할 수 있습니다 std::rel_ops.

그러나 std::rel_ops예상하지 않은 유형에 대해 비교 연산자를 추론 할 수 있으므로 사용시주의해야합니다 .

관련 연산자를 기본 연산자에서 추론하는 더 바람직한 방법은 boost :: operators 를 사용하는 것입니다. 입니다.

boost에 사용되는 접근 방식은 범위의 모든 클래스가 아니라 원하는 클래스의 연산자 사용법을 정의하기 때문에 더 좋습니다.

"+ =",- "-="등에서 "+"를 생성 할 수도 있습니다 ( 여기에서 전체 목록 참조 ).


연산자 !=를 작성한 후 기본값 을 얻지 못했습니다 ==. 또는 나는했지만 마음이 부족했습니다 const. 나 자신도 그것을 써야했다. 그리고 모두는 건강했다.
John

당신은 필요한 결과를 달성하기 위해 constness를 가지고 놀 수 있습니다. 코드가 없으면 무엇이 잘못되었는지 말하기가 어렵습니다.
sergtk

2
rel_opsC ++ 20에서는 더 이상 사용되지 않는 이유 가 있습니다. 적어도 어느 곳에서나 작동 하지 않고 지속적으로 작동하지 않기 때문 입니다. sort_decreasing()컴파일 할 수있는 확실한 방법은 없습니다 . 반면 Boost.Operators 는 작동하며 항상 작동합니다.
Barry

10

C ++ 0x 에는 기본 함수에 대한 제안 있었으므로 default operator==; 이러한 것들을 명시 적으로 만드는 것이 도움이 되었다고 말할 수 있습니다.


3
"특수 멤버 함수"(기본 생성자, 복사 생성자, 할당 연산자 및 소멸자) 만 명시 적으로 기본값으로 지정할 수 있다고 생각했습니다. 그들은 이것을 다른 운영자에게까지 확장 했습니까?
Michael Burr

4
이동 생성자도 기본값으로 설정할 수 있지만 이것이 적용되지는 않습니다 operator==. 동정입니다.
Pavel Minaev

5

개념적으로 평등을 정의하는 것은 쉽지 않습니다. POD 데이터의 경우에도 필드가 동일하더라도 다른 주소 (다른 주소)의 객체가 반드시 같을 필요는 없다고 주장 할 수 있습니다. 이것은 실제로 운영자의 사용법에 달려 있습니다. 불행히도 컴파일러는 심령이 아니며 그것을 유추 할 수 없습니다.

이 외에도 기본 기능은 발로 자신을 촬영하는 훌륭한 방법입니다. 설명하는 기본값은 기본적으로 POD 구조체와의 호환성을 유지하기위한 것입니다. 그러나 개발자가 기본 구현의 의미를 잊어 버릴 정도로 혼란을 겪습니다.


10
POD 구조에는 모호성이 없습니다. 다른 POD 유형과 동일한 방식으로 작동해야합니다. 즉, 값 평등 (참조 평등)입니다. 하나 int서로 복사본의 ctor 통해 생성은 생성되는 것과 동일하다; structint필드 중 하나 에 대해 논리적으로해야 할 일은 똑같은 방식으로 작동하는 것입니다.
Pavel Minaev

1
@ mgiuca : 값으로 작동하는 모든 유형을 사전 또는 유사한 컬렉션의 키로 사용할 수있는 보편적 동등성 관계에 대해 상당한 유용성을 볼 수 있습니다. 그러나 이러한 컬렉션은 보장-반사성 동등성 관계 없이는 유용하게 작동 할 수 없습니다. IMHO의 가장 좋은 해결책은 모든 내장 유형이 현명하게 구현할 수있는 새로운 연산자를 정의하고, 기존의 포인터 유형과 같은 일부 새로운 포인터 유형을 정의하는 것입니다. 동등성 연산자.
supercat

1
@supercat 유추하여, +수레와 비연 관적이라는 점 에서 연산자에 대해 거의 같은 주장을 할 수 있습니다 . 입니다 (x + y) + z! = x + (y + z), 방식 FP의 반올림으로 인해 발생합니다. (이것은 ==일반적인 숫자 값 보다 사실이기 때문에 훨씬 더 나쁜 문제 입니다.) 모든 숫자 유형 (int int)에 대해 작동하며 거의 동일 +하지만 연관성이 있는 새 덧셈 연산자를 추가하는 것이 좋습니다 ( 어쩐지). 그러나 많은 사람들을 도와주지 않고 언어에 혼란과 혼란을 더할 것입니다.
mgiuca

1
@ mgiuca : 가장자리 경우를 제외하고는 매우 유사한 것들을 갖는 것이 종종 매우 유용하며, 그러한 것들을 피하려는 잘못된 노력은 많은 불필요한 복잡성을 초래합니다. 클라이언트 코드가 때때로 엣지 케이스를 한 가지 방법으로 처리해야하고 때로는 다른 방식으로 처리해야하는 경우 각 스타일의 처리 방법을 사용하면 클라이언트에서 엣지 케이스 처리 코드가 많이 제거됩니다. 당신의 비유에 관해서는, 모든 경우에 전 이적 결과를 산출하기 위해 고정 크기의 부동 소수점 값에 대한 연산을 정의 할 수있는 방법은 없습니다 (일부 1980 년대 언어는 더 나은 의미론을
가졌지 만

1
... 오늘날의 관점에서 볼 때) 불가능한 일을하지 않는다는 사실은 놀라운 일이 아닙니다. 그러나 복사 할 수있는 모든 유형의 가치에 보편적으로 적용되는 동등성 관계를 구현하는 데 근본적인 장애물은 없습니다.
supercat

1

이것에 대한 정당한 이유가 있습니까? 멤버 별 비교를 수행하는 것이 왜 문제가됩니까?

기능적으로 문제가되지는 않지만 성능 측면에서 기본 구성원 별 비교는 기본 구성원 별 할당 / 복사보다 차선책 일 수 있습니다. 할당 순서와 달리 비교 순서는 첫 번째 불일치 멤버가 나머지를 건너 뛸 수 있음을 나타내므로 성능에 영향을줍니다. 따라서 일반적으로 동일한 멤버가있는 경우 마지막으로 비교하려는 경우 컴파일러는 어떤 멤버가 같은지 더 잘 모릅니다.

verboseDescription비교적 작은 가능한 날씨 설명 세트에서 선택된 긴 문자열이있는 이 예를 고려하십시오 .

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(물론 컴파일러는 부작용이 없다는 것을 인식하면 비교 순서를 무시할 권리가 있지만 아마도 더 나은 정보가없는 소스 코드에서 여전히 퀘스트를 취할 것입니다.)


그러나 성능 문제가 발견되면 최적화 된 사용자 정의 비교를 작성하지 않아도됩니다. 내 경험상 그것은 소수의 경우 일 것입니다.
피터-복원 모니카

1

시간이 지남에 따라이 질문에 대한 답변이 완성 된 상태로 유지됩니다 .C ++ 20부터는 명령으로 자동 생성 될 수 있습니다 auto operator<=>(const foo&) const = default;

모든 연산자를 생성합니다 : ==,! =, <, <=,> 및> = . 자세한 내용 은 https://en.cppreference.com/w/cpp/language/default_comparisons 를 참조하십시오.

운영자의 모양 때문에 <=>우주선 연산자라고합니다. 또한 C ++에서 우주선 <=> 연산자가 필요한 이유는 무엇입니까?를 참조하십시오 . .

편집 : C ++ 11에서도 https://en.cppreference.com/w/cpp/utility/tuple/tiestd::tie참조 하십시오bool operator<(…) . 흥미로운 부분 ==은 다음 과 같이 변경되었습니다 .

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

std::tie 모든 비교 연산자와 함께 작동하며 컴파일러에 의해 완전히 최적화됩니다.


-1

POD 유형 클래스의 경우 컴파일러가 대신 할 수 있다는 데 동의합니다. 그러나 단순하다고 생각할 수있는 컴파일러는 잘못 될 수 있습니다. 따라서 프로그래머가 그렇게하는 것이 좋습니다.

두 필드가 고유 한 POD 사례가 있었으므로 비교는 결코 사실로 간주되지 않습니다. 그러나 필자는 비교가 페이로드와 비교했을 때만 필요했습니다. 컴파일러는 결코 이해하지 못하거나 스스로 알아낼 수 없었습니다.

게다가-그들이 쓰는 데 오래 걸리지 않습니까?!

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