it-swarm-ko.com

연산자 오버로딩의 기본 규칙과 관용구는 무엇입니까?

참고 : 답변은 특정 순서 로 주어졌지만 많은 사용자가 주어진 시간이 아닌 투표에 따라 답변을 정렬하므로 여기에 답변의 색인

(참고 :이 항목은 Stack Overflow 's C++ FAQ 이 항목에서 FAQ를 제공하는 아이디어를 비판하고 싶다면 , 모든 것을 시작한 메타에 대한 게시 그 장소가 될 것입니다. 그 질문에 대한 답변은 C++ chatroom 에서 모니터링됩니다. 여기서 FAQ아이디어가 처음부터 시작되었으므로 아이디어가 떠오른 사람들이 대답을 읽을 가능성이 높습니다.)

2010
sbi

과부하를 일으키는 일반적인 연산자

과부하 연산자의 대부분의 작업은 보일러 플레이트 코드입니다. 연산자가 단순히 구문 설탕이기 때문에 실제 작업이 일반 기능으로 수행 될 수 있으며 종종 전달됩니다. 그러나이 보일러 플레이트 코드를 올바르게 얻는 것이 중요합니다. 실패하면 운영자 코드가 컴파일되지 않거나 사용자 코드가 컴파일되지 않거나 사용자 코드가 놀랍게 동작합니다.

할당 연산자

과제에 대해 할 말이 많습니다. 그러나 대부분은 이미 GMan의 유명한 Copy-And-Swap FAQ 에서 언급되었으므로 참조를 위해 완벽한 할당 연산자 만 나열하면 여기서 대부분 생략합니다.

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

비트 시프트 연산자 (스트림 I/O에 사용)

비트 시프트 연산자 <<>>는 C에서 상속 한 비트 조작 함수에 대한 하드웨어 인터페이스에서 여전히 사용되지만 대부분의 응용 프로그램에서 오버로드 된 스트림 입력 및 출력 연산자로 널리 퍼져 있습니다. 비트 조작 연산자로서 오버로드에 대한 지침은 이진 산술 연산자에 대한 아래 섹션을 참조하십시오. 객체가 iostream과 함께 사용될 때 사용자 지정 형식 및 구문 분석 논리를 구현하려면 계속하십시오.

가장 일반적으로 오버로드 된 연산자 중 스트림 연산자는 이진 삽입 연산자이며 구문은 멤버인지 멤버가 아닌지에 대한 제한을 지정하지 않습니다. 왼쪽 인수를 변경하기 때문에 (스트림의 상태를 변경 함) 경험 법칙에 따라 왼쪽 피연산자 유형의 멤버로 구현해야합니다. 그러나 왼쪽 피연산자는 표준 라이브러리의 스트림이며 표준 라이브러리에 의해 정의 된 대부분의 스트림 출력 및 입력 연산자는 실제로 자신의 유형에 대한 출력 및 입력 조작을 구현할 때 스트림 클래스의 멤버로 정의됩니다. 표준 라이브러리의 스트림 유형을 변경할 수 없습니다. 따라서 멤버가 아닌 함수로 자신의 유형에 대해 이러한 연산자를 구현해야합니다. 두 가지의 정식 형태는 다음과 같습니다.

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

operator>>를 구현할 때 스트림 상태를 수동으로 설정하는 것은 읽기 자체가 성공한 경우에만 필요하지만 결과는 예상과 다릅니다.

함수 호출 연산자

Functors라고도하는 함수 객체를 만드는 데 사용되는 함수 호출 연산자는member함수로 정의되어야하므로 항상 멤버의 암시 적 this 인수를 갖습니다. 기능. 이 외에는 0을 포함하여 여러 개의 추가 인수를 사용하도록 오버로드 될 수 있습니다.

구문의 예는 다음과 같습니다.

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

용법:

foo f;
int a = f("hello");

C++ 표준 라이브러리 전체에서 함수 객체는 항상 복사됩니다. 따라서 자신의 함수 객체는 복사하기에 저렴해야합니다. 함수 객체가 복사하는 데 비용이 많이 드는 데이터를 절대적으로 사용해야하는 경우 해당 데이터를 다른 곳에 저장하고 함수 객체가 참조하는 것이 좋습니다.

비교 연산자

이진 접두사 비교 연산자는 경험 법칙에 따라 비 멤버 함수로 구현해야합니다.1. 단항 접두어 부정 !는 (같은 규칙에 따라) 멤버 함수로 구현되어야합니다. (하지만 일반적으로 과부하는 좋지 않습니다.)

표준 라이브러리의 알고리즘 (예 : std::sort()) 및 유형 (예 : std::map)은 항상 operator< 만있을 것으로 예상합니다. 그러나 귀하의 유형의 사용자는 다른 모든 연산자가있을 것으로 예상됩니다이므로 operator<를 정의하면 연산자 오버로드의 세 번째 기본 규칙을 따르고 다른 모든 부울 비교도 정의해야합니다. 연산자. 그것들을 구현하는 정식 방법은 다음과 같습니다.

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

여기서 주목해야 할 중요한 것은이 연산자 중 두 개만 실제로 무언가를 수행하고, 다른 작업자는 실제 작업을 수행하기 위해이 두 가지 중 하나에 인수를 전달하는 것입니다.

나머지 이진 부울 연산자 (||, &&)를 오버로드하는 구문은 비교 연산자의 규칙을 따릅니다. 그러나 매우이 경우에 적절한 사용 사례를 찾지 못할 것입니다.2.

1모든 경험 법칙과 마찬가지로 때때로이 규칙을 어기는 이유가있을 수 있습니다. 그렇다면 멤버 함수에 대해 *this 인 이진 비교 연산자의 왼쪽 피연산자도 const이어야합니다. 따라서 멤버 함수로 구현 된 비교 연산자에는 다음과 같은 서명이 있어야합니다.

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(끝 부분의 const에 유의하십시오.)

2||&&의 기본 제공 버전은 바로 가기 의미를 사용합니다. 사용자가 정의한 것은 (메소드 호출의 구문 설탕이기 때문에) 바로 가기 시맨틱을 사용하지 않습니다. 사용자는 이러한 연산자에 바로 가기 의미가있을 것으로 예상되며 코드에 따라 달라질 수 있으므로 절대로 정의하지 않는 것이 좋습니다.

산술 연산자

단항 산술 연산자

단항 증가 및 감소 연산자는 접두사와 접미사 형식으로 제공됩니다. 다른 것을 구별하기 위해 접미사 변형은 추가 더미 int 인수를 사용합니다. 증분 또는 감소에 과부하가 걸리면 항상 접두사 및 접미사 버전을 모두 구현해야합니다. 다음은 증가의 표준 구현입니다. 감소는 동일한 규칙을 따릅니다.

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

접미사 변형은 접두사 측면에서 구현됩니다. 또한 postfix는 추가 사본을 수행합니다.2

단항 빼기 및 더하기 과부하는 흔하지 않으며 피하는 것이 가장 좋습니다. 필요한 경우 멤버 함수로 오버로드되어야합니다.

2또한 접미사 변형이 더 많은 작업을 수행하므로 접두사 변형보다 사용 효율이 떨어집니다. 이것은 일반적으로 접두사 증가보다 접두사 증가를 선호하는 좋은 이유입니다. 컴파일러는 일반적으로 내장 유형에 대한 추가 접미사 증가 작업을 최적화 할 수 있지만 사용자 정의 유형 (목록 반복자처럼 순진하게 보일 수 있음)에 대해서도 동일한 작업을 수행하지 못할 수 있습니다. i++에 익숙해지면 i이 내장 유형이 아닌 경우 (또한 유형을 변경할 때 코드를 변경해야 함) 대신 ++i를 수행하는 것을 기억하기가 매우 어려워집니다. postfix가 명시 적으로 필요하지 않으면 접두사 증가를 항상 사용하는 습관을들이는 것이 좋습니다.

이진 산술 연산자

이진 산술 연산자의 경우 세 번째 기본 규칙 연산자 오버로드를 준수하는 것을 잊지 마십시오. +를 제공하는 경우 +=를 제공하고 -를 제공하는 경우 -= 등을 생략하지 마십시오. Andrew Koenig는 가장 먼저 관찰 한 것으로 알려져 있습니다. 복합 할당 연산자는 비 복합 연산자의 기반으로 사용할 수 있습니다. 즉, 연산자 ++=의 측면에서 구현되고 --= 등의 측면에서 구현됩니다.

우리의 경험칙에 따르면 +와 그 동반자는 비회원이어야하며, 왼쪽 인수를 변경하는 복합 할당 상대 (+= 등)는 회원이어야합니다. 다음은 +=+의 예제 코드입니다. 다른 이진 산술 연산자는 같은 방식으로 구현되어야합니다.

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+=는 참조 당 결과를 반환하고 operator+는 결과 복사본을 반환합니다. 물론, 참조를 반환하는 것이 일반적으로 복사본을 반환하는 것보다 효율적이지만 operator+의 경우 복사 주위에 방법이 없습니다. a + b를 쓰면 결과가 새로운 값이되기를 기대하므로 operator+가 새로운 값을 반환해야합니다. 또한 operator+는 const 참조가 아닌 왼쪽 피연산자copy by를 사용합니다. 그 이유는 operator=가 복사 본당 인수를 취하는 이유와 같습니다.

비트 조작 연산자 ~&|^<<>>는 산술 연산자와 같은 방식으로 구현해야합니다. 그러나 (출력 및 입력에 대해 <<>>를 오버로드하는 경우 제외) 이러한 오버로드에 대한 합리적인 사용 사례는 거의 없습니다.

여기서도 교훈은 a += b는 일반적으로 a + b보다 효율적이며 가능한 경우 선호하는 것입니다.

배열 첨자

배열 첨자 연산자는 이진 연산자이며 클래스 멤버로 구현해야합니다. 키로 데이터 요소에 액세스 할 수있는 컨테이너와 같은 유형에 사용됩니다. 이를 제공하는 정식 형태는 다음과 같습니다.

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

클래스의 사용자가 operator[]에 의해 반환 된 데이터 요소를 변경하지 못하게하려면 (비정규 변형을 생략 할 수있는 경우) 항상 연산자의 두 변형을 모두 제공해야합니다.

Value_type이 내장 유형을 참조하는 것으로 알려진 경우 연산자의 const 변형은 const 참조 대신 복사본을 반환하는 것이 좋습니다.

class X {
  value_type& operator[](index_type idx);
  value_type  operator[](index_type idx) const;
  // ...
};

포인터와 같은 유형의 연산자

자체 반복자 또는 스마트 포인터를 정의하려면 단항 접두사 역 참조 연산자 * 및 이진 접두사 포인터 멤버 액세스 연산자 ->를 오버로드해야합니다.

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

이것들도 거의 항상 const와 non-const 버전을 필요로합니다. -> 연산자의 경우 value_typeclass (또는 struct 또는 union) 유형 인 경우 operator->()이 값을 반환 할 때까지 다른 operator->()을 재귀 적으로 호출합니다. 비 클래스 타입의.

단항 주소 연산자에 과부하가 걸리지 않아야합니다.

operator->*()에 대해서는 이 질문 을 참조하십시오. 거의 사용되지 않으므로 과부하가 걸리지 않습니다. 실제로 반복자조차도 과부하되지 않습니다.


계속 변환 연산자

991
sbi

C++에서 연산자 오버로드의 3 가지 기본 규칙

C++에서 연산자 오버로드와 관련하여 세 가지 기본 규칙이 있습니다. 이러한 모든 규칙과 마찬가지로 실제로 예외가 있습니다. 때때로 사람들은 그들로부터 벗어 났고 결과는 나쁜 코드가 아니었지만 그러한 긍정적 인 편차는 거의 없습니다. 적어도 내가 본 100 가지 중 99 가지의 편차는 정당화되지 않았습니다. 그러나 1000 개 중 999 개일 수도 있습니다. 따라서 다음 규칙을 따르는 것이 좋습니다.

  1. 연산자의 의미가 분명하지 않고 논쟁의 여지가없는 경우에는 과부하가 걸리지 않아야합니다.대신, 잘 선택된 이름으로 함수를 제공하십시오.
    기본적으로 운영자에게 과부하를 가하는 가장 중요한 첫 번째 규칙은 다음과 같습니다. 하지 말아라. 연산자 오버로드에 대해 알려진 것이 많고 많은 기사, 서적 장 및 기타 텍스트가이 모든 것을 다루기 때문에 이상하게 보일 수 있습니다. 그러나이 명백한 증거에도 불구하고 연산자 과부하가 적절한 경우는 놀랍게도 거의 없습니다. 그 이유는 애플리케이션 도메인에서 운영자의 사용이 잘 알려져 있고 논쟁의 여지가 없다면 운영자의 애플리케이션 뒤에 의미를 이해하기 어렵 기 때문입니다. 대중의 신념과는 달리 이것은 거의 사실이 아닙니다.

  2. 언제나 연산자의 잘 알려진 의미를 고수합니다.
    C++는 오버로드 된 연산자의 의미에 제한이 없습니다. 컴파일러는 이진 + 연산자를 구현하여 올바른 피연산자를 빼는 코드를 기꺼이 받아들입니다. 그러나 이러한 연산자의 사용자는 a에서 b을 빼는 식 a + b을 의심하지 않습니다. 물론 이것은 응용 프로그램 도메인에서 연산자의 의미론이 논란의 여지가 없다고 가정합니다.

  3. 항상 모든 관련 작업 집합을 제공하십시오.
    운영자는 서로 관련이 있습니다 및 다른 작업과. 유형이 a + b를 지원하면 사용자는 a += b에게 전화 할 수도 있습니다. 접두어 증가 ++a를 지원하면 a++도 작동 할 것으로 예상합니다. a < b 여부를 확인할 수 있다면 a > b 여부도 확인할 수있을 것입니다. 그들이 당신의 유형을 복사 구성 할 수 있다면, 그들은 또한 과제가 잘 작동 할 것으로 기대합니다.


회원과 비회원 간의 결정 으로 계속 진행하십시오.

473
sbi

C++에서 연산자 오버로드의 일반적인 구문

C++에서 내장 유형에 대한 연산자의 의미를 변경할 수 없으며 연산자는 사용자 정의 유형에 대해서만 오버로드 될 수 있습니다.1. 즉, 피연산자 중 적어도 하나는 사용자 정의 유형이어야합니다. 다른 오버로드 된 기능과 마찬가지로 특정 매개 변수 집합에 대해 연산자를 한 번만 오버로드 할 수 있습니다.

모든 연산자가 C++에서 오버로드 될 수있는 것은 아닙니다. 오버로드 할 수없는 연산자 중에는 .::sizeoftypeid.* 및 C++의 유일한 삼항 연산자 인 ?:

C++에서 오버로드 될 수있는 연산자는 다음과 같습니다.

  • 산술 연산자 : +-*/%+=-=*=/=%= (모든 이진 접두사); +- (단항 접두사); ++-- (단항 접두사 및 접미사)
  • 비트 조작 : &|^<<>>&=|=^=<<=>>= (모든 이진 접두사); ~ (단항 접두사)
  • 부울 대수 : ==!=<><=>=||&& (모든 이진 수정); ! (단항 접두사)
  • 메모리 관리 : newnew[]deletedelete[]
  • 암시 적 변환 연산자
  • 기타 : =[]->->*, (모든 이진 접두사); *& (모든 단항 접두어) () (함수 호출, n- 진 접두사)

그러나 can 이 모든 항목에 과부하가 걸린다고해서 should 그렇게하는 것은 아닙니다. 운영자 과부하의 기본 규칙을 참조하십시오.

C++에서 연산자는 특수 이름 형식으로 오버로드됩니다. 다른 함수와 마찬가지로 오버로드 된 연산자는 일반적으로 왼쪽 피연산자의 type 또는 비 멤버 함수 . 어느 쪽이든 자유롭게 사용할 수 있는지 또는 사용할 수 있는지 여부는 몇 가지 기준에 따라 다릅니다.2 단항 연산자 @객체 x에 적용되는 _은 (는) [email protected](x) 또는 [email protected]()으로 호출됩니다. xy 개체에 적용되는 이진 접두사 연산자 @[email protected](x,y) 또는 [email protected](y)이라고합니다.4

비 멤버 함수로 구현 된 연산자는 때때로 피연산자 유형의 친구입니다.

1"사용자 정의"라는 용어는 약간 잘못 될 수 있습니다. C++은 내장 타입과 사용자 정의 타입을 구별합니다. 전자는 예를 들어 int, char 및 double에 속합니다. 후자는 표준 라이브러리의 것을 포함하여 모든 구조체, 클래스, 공용체 및 열거 형에 속하지만 사용자가 정의하지는 않습니다.

2이 FAQ의 나중 부분 에서 다룹니다.

@는 C++에서 유효한 연산자가 아니기 때문에 자리 표시 자로 사용합니다.

4C++의 유일한 삼항 연산자는 오버로드 될 수 없으며 유일한 n- 항 연산자는 항상 멤버 함수로 구현되어야합니다.


C++에서 연산자 오버로드의 세 가지 기본 규칙 로 계속 진행하십시오.

252
sbi

회원과 비회원의 결정

이진 연산자 = (할당), [] (배열 가입), -> (회원 액세스) 및 n-ary () (함수 호출) 연산자, 언어의 구문에 필요하기 때문에 항상 member functions로 구현해야합니다.

다른 연산자는 멤버 또는 비 멤버로 구현할 수 있습니다. 그러나 일부는 왼쪽 피연산자를 수정할 수 없으므로 일반적으로 비 멤버 함수로 구현해야합니다. 가장 눈에 띄는 것은 입력 및 출력 연산자 <<>>이며, 왼쪽 피연산자는 변경할 수없는 표준 라이브러리의 스트림 클래스입니다.

멤버 함수 또는 비 멤버 함수로 구현하도록 선택해야하는 모든 연산자의 경우 다음 규칙 사용 :

  1. 단항 연산자 인 경우 member 기능.
  2. 이항 연산자가 두 피연산자 모두 동일하게]를 처리하는 경우 (이는 변경되지 않음)이 연산자를 non -멤버 함수.
  3. 이항 연산자가 not 인 경우 두 피연산자를 모두 처리합니다 equally (보통 왼쪽 피연산자를 변경합니다), 액세스 해야하는 경우 왼쪽 피연산자 유형의 기능을 member 함수로 만드는 것이 유용 할 수 있습니다 피연산자의 개인 부분.

물론 모든 경험 규칙과 마찬가지로 예외가 있습니다. 유형이 있다면

enum Month {Jan, Feb, ..., Nov, Dec}

그리고 증가 및 감소 연산자를 오버로드하려면 C++에서 열거 형 유형에 멤버 함수를 사용할 수 없으므로 멤버 함수 로이 작업을 수행 할 수 없습니다. 따라서 무료 기능으로 과부하해야합니다. 그리고 클래스 템플릿 내에 중첩 된 클래스 템플릿의 경우 operator<()은 클래스 정의에서 인라인 멤버 함수로 수행 할 때 훨씬 쉽게 읽고 쓸 수 있습니다. 그러나 이것들은 실제로 드문 예외입니다.

그러나 if 예외를 만들 경우 멤버 함수의 경우 암시 적 const 인수가되는 피연산자의 this- ness 문제를 잊지 마십시오. 비 멤버 함수로서의 연산자는 가장 왼쪽 인수를 const 참조로 사용합니다. 멤버 함수와 같은 연산자는 끝에 const이 있어야 *this a const 참조)


일반적인 연산자 오버로드 로 계속 진행하십시오.

233
sbi

전환 연산자 (사용자 정의 전환이라고도 함)

C++에서는 컴파일러가 유형과 다른 정의 된 유형을 변환 할 수 있도록하는 변환 연산자, 연산자를 만들 수 있습니다. 변환 연산자에는 암시 적 및 명시 적 연산자의 두 가지 유형이 있습니다.

암시 적 변환 연산자 (C++ 98/C++ 03 및 C++ 11)

암시 적 변환 연산자를 사용하면 컴파일러에서 사용자 정의 유형의 값을 다른 유형으로 암시 적으로 변환 할 수 있습니다 (예 : intlong 간의 변환).

다음은 암시 적 변환 연산자가있는 단순 클래스입니다.

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

인수가 하나 인 생성자와 같은 암시 적 변환 연산자는 사용자 정의 변환입니다. 컴파일러는 오버로드 된 함수에 대한 호출을 일치 시키려고 할 때 하나의 사용자 정의 변환을 허용합니다.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

처음에는 이것이 매우 도움이되는 것처럼 보이지만,이 문제는 암시 적 변환이 예상치 못한 경우에도 시작된다는 것입니다. 다음 코드에서는 void f(const char*)lvalue 가 아니기 때문에 my_string()이 호출되므로 첫 번째 항목과 일치하지 않습니다.

void f(my_string&);
void f(const char*);

f(my_string());

초보자도 쉽게 잘못 이해하고 심지어 경험이 많은 C++ 프로그래머는 컴파일러가 의심치 않았던 과부하를 선택하기 때문에 때때로 놀라곤합니다. 이러한 문제는 명시 적 변환 연산자로 완화 할 수 있습니다.

명시 적 변환 연산자 (C++ 11)

암시 적 변환 연산자와 달리 명시 적 변환 연산자는 예상치 못한 경우에는 절대 실행되지 않습니다. 다음은 명시 적 변환 연산자가있는 간단한 클래스입니다.

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

explicit에 주목하십시오. 이제 암시 적 변환 연산자에서 예기치 않은 코드를 실행하려고하면 컴파일러 오류가 발생합니다.

 prog.cpp : 함수에서 'int main ()': 
 prog.cpp : 15 : 18 : 오류 : 'f (my_string)'호출에 대해 일치하는 함수가 없습니다. 
 prog.cpp : 15 : 18 : 참고 : 후보자는 다음과 같습니다. 
 prog.cpp : 11 : 10 : 참고 : void f (my_string &) 
 prog.cpp : 11 : 10 : 'my_string'에서 'my_string'으로의 변환 1 
 prog.cpp : 12 : 10 : 참고 : void f (const char *) 
 prog.cpp : 12 : 10 : 참고 : 아니오 인수 1을 'my_string'에서 'const char *'로 변환하는 알려진 변환 

명시 적 형변환 연산자를 호출하려면 static_cast, C 형 형 변환 또는 생성자 스타일 형 변환 (예 : T(value))을 사용해야합니다.

그러나 예외는 하나 있습니다. 컴파일러는 암시 적으로 bool으로 변환 할 수 있습니다. 또한 컴파일러는 bool으로 변환 한 후 다른 암시 적 변환을 수행 할 수 없습니다 (컴파일러는 한 번에 2 개의 암시 적 변환을 수행 할 수 있지만 최대 1 개의 사용자 정의 변환 만 수행 할 수 있습니다).

컴파일러가 "과거"bool을 캐스팅하지 않기 때문에 명시 적 변환 연산자는 이제 Safe Bool 관용구 의 필요성을 제거합니다. 예를 들어 C++ 11 이전의 스마트 포인터는 Safe Bool 관용구를 사용하여 필수 유형으로의 변환을 방지했습니다. C++ 11에서 스마트 포인터는 컴파일러가 유형을 명시 적으로 bool로 변환 한 후에 암시 적으로 정수 유형으로 변환 할 수 없으므로 대신 명시 적 연산자를 사용합니다.

계속 newdelete 오버로드.

152
JKor

newdelete 오버로드

참고 : 이것은 newdelete 오버로드의 syntax 만 다루며, implementation 과부하 된 연산자. 오버로드의 의미 newdelete은 자체 FAQ가 필요합니다), 연산자 오버로드 주제 내에서는 결코 정의를 할 수 없다고 생각합니다.

기초

C++에서 new T(arg)과 같은 new expression을 작성하면이 표현식이 평가 될 때 두 가지가 발생합니다. 먼저 operator new이 호출됩니다. 원시 메모리를 연 다음 T의 해당 생성자를 호출하여이 원시 메모리를 유효한 객체로 변환합니다. 마찬가지로 객체를 삭제하면 먼저 소멸자가 호출 된 다음 메모리가 _operator delete_로 반환됩니다.
C++를 사용하면 메모리 관리 및 할당 된 메모리에서 개체의 구성/파괴와 같은 두 가지 작업을 모두 조정할 수 있습니다. 후자는 클래스의 생성자와 소멸자를 작성하여 수행됩니다. 미세 조정 메모리 관리는 자체 _operator new_ 및 _operator delete_을 작성하여 수행됩니다.

연산자 오버로드의 첫 번째 기본 규칙 – 하지 말 것newdelete 오버로드에 특히 적용됩니다. 이러한 연산자를 오버로드하는 거의 유일한 이유는 performance problemsmemory constraints이며, 대부분의 경우 알고리즘 변경과 같은 다른 작업을 (를) 사용하면 메모리 관리 조정보다 많은 높은 비용/이득 비율을 제공합니다.

C++ 표준 라이브러리에는 사전 정의 된 newdelete 연산자 세트가 제공됩니다. 가장 중요한 것은 다음과 같습니다.

_void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 
_

처음 2 개는 객체에 메모리를 할당/할당하고, 2 개는 객체 배열에 할당합니다. 자신의 버전을 제공하면 표준 라이브러리의 과부하가 아니라} 대체됩니다.
_operator new_을 오버로드하는 경우 호출하려는 의도가 없더라도 일치하는 _operator delete_도 항상 오버로드해야합니다. 그 이유는 새 표현식을 평가하는 동안 생성자가 발생하면 런타임 시스템은 메모리를 할당하기 위해 호출 된 _operator delete_와 일치하는 _operator new_에 메모리를 반환하기 때문입니다. 일치하는 _operator delete_을 제공하지 않으면 기본 이름이 호출되는데 이는 거의 항상 잘못된 것입니다.
newdelete을 오버로드하는 경우 배열 변형도 오버로드하는 것을 고려해야합니다.

게재 위치 new

C++에서는 new 및 delete 연산자가 추가 인수를 취할 수 있습니다.
소위 게재 위치 새로 만들기를 사용하면 다음 주소로 전달되는 특정 주소에서 개체를 만들 수 있습니다.

_class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 
_

표준 라이브러리는이를 위해 new 및 delete 연산자의 적절한 과부하를 제공합니다.

_void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 
_

위에 주어진 새로운 배치를위한 예제 코드에서 X의 생성자가 예외를 throw하지 않는 한 _operator delete_는 호출되지 않습니다.

newdelete을 다른 인수로 오버로드 할 수도 있습니다. 새로운 배치를위한 추가 인수와 마찬가지로 이러한 인수는 키워드 new 뒤에 괄호 안에 표시됩니다. 역사적 이유로, 이러한 변형은 인수가 특정 주소에 객체를 배치하기위한 것이 아니더라도 종종 새로운 배치라고도합니다.

클래스 별 신규 및 삭제

측정 결과 특정 클래스 또는 관련 클래스 그룹의 인스턴스가 자주 생성 및 소멸되고 런타임 시스템의 기본 메모리 관리가 조정되었음을 보여 주므로 메모리 관리를 세부적으로 조정하려고합니다. 일반적인 성능은이 특정 사례에서 비효율적으로 처리됩니다. 이를 개선하기 위해 특정 클래스에 대해 새로운 과부하 및 삭제를 수행 할 수 있습니다.

_class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 
_

따라서 과부하가 걸리면 new 및 delete는 정적 멤버 함수처럼 작동합니다. _my_class_ 객체의 경우 _std::size_t_ 인수는 항상 sizeof(my_class)입니다. 그러나 이러한 연산자는 파생 클래스_의 동적 할당 개체에 대해서도 호출됩니다.이 경우 이보다 클 수 있습니다.

글로벌 신규 및 삭제

새로운 전역 과부하 및 삭제를 수행하려면 표준 라이브러리의 사전 정의 된 연산자를 자체 연산자로 바꾸십시오. 그러나 이것은 거의 수행 할 필요가 없습니다.

144
sbi

operator<<std::cout 함수를 스트리밍 할 수 없거나 파일에 멤버 함수로 사용할 수 없습니까?

당신이 가지고 있다고 가정 해 봅시다 :

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

그것을 감안할 때, 당신은 사용할 수 없습니다 :

Foo f = {10, 20.0};
std::cout << f;

operator<<Foo의 멤버 함수로 오버로드되므로 연산자의 LHS는 Foo 객체 여야합니다. 즉, 다음을 사용해야합니다.

Foo f = {10, 20.0};
f << std::cout

매우 비 직관적입니다.

비 멤버 함수로 정의하면,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

다음을 사용할 수 있습니다.

Foo f = {10, 20.0};
std::cout << f;

매우 직관적입니다.

37
R Sahu