etc./StackOverFlow

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

청렴결백한 만능 재주꾼 2021. 11. 25. 06:53
반응형

질문자 :sbi


참고: 답변은 특정 순서 로 제공되었지만 많은 사용자가 답변을 주어진 시간이 아니라 투표에 따라 정렬하기 때문에 다음은 가장 의미 있는 답변의 색인입니다.

(참고: 이것은 스택 오버플로의 C++ FAQ에 대한 항목입니다. 이 형식으로 FAQ를 제공하는 아이디어를 비판하고 싶다면 이 모든 것을 시작한 메타에 게시하는 것이 그렇게 할 수 있는 장소가 될 것입니다. 그 질문은 FAQ 아이디어가 처음 시작된 C++ 대화방 에서 모니터링되므로 아이디어를 생각해낸 사람들이 귀하의 답변을 읽을 가능성이 매우 높습니다.)



오버로드하는 일반 연산자

연산자 오버로딩 작업의 대부분은 상용구 코드입니다. 연산자는 단지 구문상의 설탕이기 때문에 실제 작업은 일반 함수에 의해 수행될 수 있습니다(종종 전달됨). 그러나 이 상용구 코드를 올바르게 이해하는 것이 중요합니다. 실패하면 운영자의 코드가 컴파일되지 않거나 사용자의 코드가 컴파일되지 않거나 사용자의 코드가 놀라울 정도로 작동합니다.

할당 연산자

과제에 대해서는 할 말이 많다. 그러나 대부분은 이미 GMan의 유명한 Copy-And-Swap FAQ 에서 언급되었으므로 여기서는 대부분을 건너뛰고 참조용으로 완벽한 할당 연산자만 나열합니다.

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

Bitshift 연산자(스트림 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>> 구현할 때 스트림의 상태를 수동으로 설정하는 것은 읽기 자체가 성공한 경우에만 필요하지만 결과는 예상한 것과 다릅니다.

함수 호출 연산자

함수 개체를 생성하는 데 사용되는 함수 호출 연산자(펑터라고도 함)는 멤버 함수로 정의되어야 하므로 항상 멤버 함수의 암시적 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개만 실제로 작업을 수행하고 다른 연산자는 실제 작업을 수행하기 위해 이 둘 중 하나에 인수를 전달한다는 것입니다.

나머지 이진 부울 연산자( || , && )를 오버로드하는 구문은 비교 연산자의 규칙을 따릅니다. 그러나 이러한 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 를 수행하는 것을 기억하기가 매우 어려워집니다(또한 유형을 변경할 때 코드를 변경해야 함). 따라서 다음을 수행하는 것이 좋습니다. 접미사가 명시적으로 필요하지 않는 한 항상 접두사 증분을 사용하는 습관을 만드십시오.

이진 산술 연산자

이항 산술 연산자의 경우 세 번째 기본 연산자 오버로딩을 준수하는 것을 잊지 마십시오. + += 도 제공 - 를 제공하면 -= 생략하지 마십시오. 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+ 는 새 값을 반환해야 합니다. 3 또한 operator+ 는 const 참조가 아닌 복사로 왼쪽 피연산자를 사용합니다. 그 이유는 operator= 가 복사본당 인수를 취하는 이유와 같습니다.

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

3 다시 말하지만, 이것에서 얻을 수 있는 교훈 a += b a + b 보다 더 효율적이며 가능하면 선호되어야 한다는 것입니다.

배열 첨자

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

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

operator[] 에서 반환된 데이터 요소를 변경할 수 없도록 하려면(이 경우 const가 아닌 변형을 생략할 수 있음) 항상 두 가지 연산자 변형을 모두 제공해야 합니다.

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_type 입니다 class (또는 struct 또는 union ) 형, 또 다른 operator->() 때까지, 재귀 호출 operator->() 비 클래스 형의 값을 반환합니다.

단항 주소 연산자는 오버로드되어서는 안 됩니다.

operator->*() 경우 이 질문을 참조하십시오. 거의 사용되지 않으므로 거의 과부하가 걸리지 않습니다. 사실, iterator조차도 그것을 오버로드하지 않습니다.


변환 연산자로 계속


sbi

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

C++에서 연산자 오버로딩과 관련하여 따라야 할 세 가지 기본 규칙이 있습니다 . 그러한 모든 규칙과 마찬가지로 실제로 예외가 있습니다. 때때로 사람들은 그들로부터 일탈했고 그 결과는 나쁜 코드가 아니었지만, 그러한 긍정적인 일탈은 극히 드물다. 적어도 내가 본 100가지 일탈 중 99가지가 정당하지 않은 일이었습니다. 하지만 1000점 만점에 999점일 수도 있습니다. 따라서 다음 규칙을 따르는 것이 좋습니다.

  1. 연산자의 의미가 명확하지 않고 논쟁의 여지가 없을 때마다 과부하되어서는 안됩니다. 대신 잘 선택된 이름으로 함수를 제공하십시오.
    기본적으로 연산자 오버로딩에 대한 첫 번째이자 가장 중요한 규칙은 핵심에 다음과 같습니다. Don't do it . 연산자 오버로딩에 대해 알아야 할 것이 많고 많은 기사, 책 장 및 기타 텍스트에서 이 모든 것을 다루기 때문에 이상하게 보일 수 있습니다. 그러나 이 명백한 증거에도 불구하고 연산자 오버로딩이 적절한 경우는 놀라울 정도로 적습니다 . 그 이유는 응용 영역에서 연산자의 사용이 잘 알려져 있고 논란의 여지가 없는 한 연산자의 응용 뒤에 있는 의미를 실제로 이해하기 어렵기 때문입니다. 대중적인 믿음과 달리 이것은 거의 사실이 아닙니다.

  2. 항상 연산자의 잘 알려진 의미를 고수하십시오.
    C++는 오버로드된 연산자의 의미에 제한을 두지 않습니다. 컴파일러는 오른쪽 피연산자에서 빼기 + 연산자를 구현하는 코드를 기꺼이 수락합니다. 그러나, 운영자의 사용자는 표현을 의심하지 않을 것이다 a + ba 에서 b . 물론 이것은 애플리케이션 도메인에서 운영자의 의미가 논쟁의 여지가 없다고 가정합니다.

  3. 항상 관련 작업 집합에서 모두를 제공합니다.
    연산자는 서로 관련되어 있고 다른 작업과 관련되어 있습니다. 유형이 a + b a += b 도 호출할 수 있을 것으로 기대합니다. ++a 지원하는 경우 a++ 도 작동할 것으로 예상합니다. a < b 인지 확인할 수 있다면 a > b 확인할 수 있을 것으로 가장 확실하게 기대할 것입니다. 그들이 당신의 유형을 복사-구성할 수 있다면 할당도 작동할 것으로 기대합니다.


회원과 비회원의 결정으로 계속 가십시오.


sbi

C++에서 연산자 오버로딩의 일반 구문

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

C++에서 모든 연산자를 오버로드할 수 있는 것은 아닙니다. 오버로드할 수 없는 연산자는 다음과 같습니다 . :: sizeof typeid .* 및 C++의 유일한 삼항 연산자, ?:

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

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

그러나 이 모든 것을 오버로드할 수 있다고 해서 반드시 그렇게 해야 하는 것은 아닙니다. 연산자 오버로딩의 기본 규칙을 참조하십시오.

C++에서 연산자는 특수 이름을 가진 함수 형태로 오버로드됩니다. 다른 함수와 마찬가지로 오버로드된 연산자는 일반적으로 왼쪽 피연산자 유형의 멤버 함수 또는 비멤버 함수로 구현될 수 있습니다. 둘 중 하나를 자유롭게 선택하거나 사용해야 하는지 여부는 여러 기준에 따라 다릅니다. 2 객체 x에 적용된 단항 연산자 @ 3 operator@(x) 또는 x.operator@() 로 호출됩니다. 객체 xy 적용된 이진 중위 연산자 @operator@(x,y) 또는 x.operator@(y) 로 호출됩니다. 4

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

1 "사용자 정의"라는 용어는 약간 오해의 소지가 있습니다. C++는 기본 제공 유형과 사용자 정의 유형을 구분합니다. 전자는 예를 들어 int, char 및 double에 속합니다. 후자는 표준 라이브러리의 유형을 포함하여 모든 struct, class, union 및 enum 유형에 속합니다. 사용자가 정의하지 않은 경우에도 마찬가지입니다.

2 이것은 이 FAQ 의 뒷부분 에서 다룹니다.

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

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


C++에서 연산자 오버로딩의 세 가지 기본 규칙으로 계속 진행합니다.


sbi

회원과 비회원의 결정

이항 연산자 = (할당), [] (배열 구독), -> (멤버 액세스) 및 n-ary () (함수 호출) 연산자는 항상 멤버 함수 로 구현되어야 합니다. 언어는 그들에게 요구합니다.

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

멤버 함수 또는 비멤버 함수로 구현하도록 선택해야 하는 모든 연산자의 경우 다음 경험적 규칙을 사용하여 결정합니다.

  1. 단항 연산자 인 경우 멤버 함수로 구현합니다.
  2. 이항 연산자가 두 피연산자를 동일하게 취급하는 경우(변경되지 않은 상태로 유지) 이 연산자를 비멤버 함수로 구현합니다.
  3. 이항 연산자가 두 피연산자를 동등하게 취급하지 않는 경우(보통 왼쪽 피연산자를 변경함) 피연산자의 개인 부분에 액세스해야 하는 경우 왼쪽 피연산자 유형의 멤버 함수로 만드는 것이 유용할 수 있습니다.

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

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

증가 및 감소 연산자를 오버로드하려면 C++에서 열거형 형식에 멤버 함수가 있을 수 없기 때문에 멤버 함수로 이 작업을 수행할 수 없습니다. 따라서 무료 함수로 오버로드해야 합니다. 그리고 operator<() 는 클래스 정의에서 인라인 멤버 함수로 수행될 때 작성 및 읽기가 훨씬 쉽습니다. 그러나 이것들은 실제로 드문 예외입니다.

당신은 예외를 만들 경우 (단, 문제 잊지 마세요 const 멤버 함수에 대한 암시되고, 그 피연산자에 -ness을 this 인수. 비 멤버 함수와 연산자로는 가장 왼쪽 인수를 할 경우 const 참조, 멤버 함수와 동일한 연산자는 *thisconst 참조로 만들기 위해 끝에 const 가 있어야 합니다.)


오버로드하려면 일반 연산자로 이동 합니다.


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*) my_string() 이 lvalue 가 아니므로 호출될 것이므로 첫 번째는 일치하지 않습니다.

 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: 참고: 무효 f(my_string&)
prog.cpp:11:10: 참고: 'my_string'에서 'my_string&'(으)로의 인수 1에 대한 알려진 변환이 없습니다.
prog.cpp:12:10: 참고: void f(const char*)
prog.cpp:12:10: 참고: 'my_string'에서 'const char*'로의 인수 1에 대한 알려진 변환이 없습니다.

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

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

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

newdelete 오버로딩을 계속합니다.


JKor

newdelete 오버로딩

참고: 이것은 오버로드된 newdelete 구문 만 다루고 오버로드된 연산자 의 구현 은 다루지 않습니다. newdelete 오버로딩의 의미론 이 그들 자신의 FAQ를 가질 자격이 있다고 생각하는데, operator 오버로딩이라는 주제 내에서 나는 결코 그것을 정의할 수 없다.

기초

C ++,이 같은 새로운 표현 쓸 때에서 new T(arg) 두 가지 일이 식을 계산할 때 발생 : 먼저 operator new 원시 메모리를 얻기 위해 호출 한 다음 적절한 생성자 T 에이 원시 메모리를 설정하는 호출 유효한 개체. 마찬가지로 객체를 삭제할 때 먼저 소멸자가 호출된 다음 메모리가 operator delete 로 반환됩니다.
C++를 사용하면 메모리 관리와 할당된 메모리에서 객체의 생성/파괴라는 두 가지 작업을 모두 조정할 수 있습니다. 후자는 클래스의 생성자와 소멸자를 작성하여 수행됩니다. 메모리 관리 미세 조정은 자신의 operator newoperator delete 를 작성하여 수행됩니다.

연산자 오버로딩의 기본 규칙 중 첫 번째( 하지 마십시오) newdelete 오버로드에 적용됩니다. 이러한 연산자를 오버로드하는 거의 유일한 이유는 성능 문제메모리 제약 이며, 많은 경우에 사용된 알고리즘 변경 과 같은 다른 작업은 메모리 관리를 조정하려고 시도하는 것보다 훨씬 더 높은 비용/이득 비율을 제공합니다.

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();

처음 두 개는 객체에 대해 메모리를 할당/할당 해제하고, 후자는 객체 배열에 대해 메모리를 할당/할당 해제합니다. 이들의 고유한 버전을 제공하면 오버로드되지 않고 표준 라이브러리의 버전을 대체합니다.
operator new 를 오버로드하는 경우 호출할 의도가 전혀 없더라도 operator delete 도 오버로드해야 합니다. 그 이유는 새 표현식을 평가하는 동안 생성자가 throw하면 런타임 시스템이 객체를 생성하기 위해 메모리를 할당하기 위해 호출된 operator new 일치하는 operator delete operator delete 제공하지 않으면 기본 연산자가 호출되며 이는 거의 항상 잘못된 것입니다.
newdelete 오버로드하는 경우 배열 변형도 오버로드하는 것을 고려해야 합니다.

new 게재위치

C++에서는 new 및 delete 연산자가 추가 인수를 사용할 수 있습니다.
소위 배치 new를 사용하면 다음으로 전달되는 특정 주소에서 객체를 생성할 수 있습니다.

 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();

위에 제공된 new 배치에 대한 예제 코드에서 X의 생성자가 예외를 throw하지 않는 한 operator delete

다른 인수로 newdelete 를 오버로드할 수도 있습니다. 배치 new에 대한 추가 인수와 마찬가지로 이러한 인수도 new 키워드 뒤의 괄호 안에 나열됩니다. 단순히 역사적 이유로 이러한 변형은 특정 주소에 개체를 배치하기 위한 인수가 아닌 경우에도 종종 새로운 배치라고도 합니다.

클래스별 신규 및 삭제

측정 결과 특정 클래스 또는 관련 클래스 그룹의 인스턴스가 자주 생성 및 소멸되고 런타임 시스템의 기본 메모리 관리가 일반적인 성능은 이 특정 경우에 비효율적으로 처리합니다. 이를 개선하기 위해 특정 클래스에 대해 new 및 delete를 오버로드할 수 있습니다.

 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) 입니다. 그러나 이러한 연산자는 파생 클래스 의 동적으로 할당된 개체에 대해서도 호출되며, 이 경우 그보다 클 수 있습니다.

글로벌 신규 및 삭제

전역 new 및 delete를 오버로드하려면 표준 라이브러리의 미리 정의된 연산자를 우리 고유의 연산자로 바꾸기만 하면 됩니다. 그러나 이 작업을 수행해야 하는 경우는 거의 없습니다.


sbi

std::cout 또는 파일로 스트리밍하기 위한 operator<< 함수가 멤버 함수가 될 수 없는 이유는 무엇입니까?

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

 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 << fa << " " << fb; }

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

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

매우 직관적입니다.


R Sahu

출처 : http:www.stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading

반응형