연산자 오버 로딩

xx.xx·2023년 11월 9일
1

c++

목록 보기
3/8

c++ 연산자 오버 로딩

  • 11.7: `operator new(size_t)' 중복 정의
    • operator new를 중복 정의할 때 반드시 반환 유형을 void *로 정의

    • 첫 매개변수 타입은 size_t

    • 기본 operator new는 매개변수를 하나만 정의하고 있지만 중복 정의 버전은 여러 개의 매개변수를 정의 가능

    • 중복 정의 operator new 멤버가 동적으로 메모리를 배당하려면 영역 지정 연산자 ::를 적용해서 전역 operator new를 사용

      /

      다음 예제에서 String 클래스의 중복 정의 operator new는 동적으로 할당된 String 객체의 실체들을 0 byte로 초기화

       #include <cstring>
          #include <iosfwd>
      
          class String
          {
              string *d_data;
      
              public:
                  void *operator new(size_t size)
                  {
                      return memset(::operator new(size), 0, size);
                  }
                  bool empty() const
                  {
                      return d_data == 0;
                  }
          };

      operator new 함수는 클래스 내에서 오버로딩되어 재정의됨

      operator new 함수를 재정의함으로써 String 클래스의 객체가 생성될 때 항상 0으로 초기화된 메모리를 가지게 되므로, 객체를 생성한 후에 초기화할 필요가 없어 편리하게 사용할 수 있다. 객체의 초기 상태를 보장하고, 메모리 누수를 방지하는 데 도움

      /

      String::String(String *const this);     // 기본 생성자의 원형
      String *sp = new String;                // 이 서술문은 다음과 같이 구현
      
      String *sp = static_cast<String *>(String::operator new(sizeof(String)) );
      String::String(sp);
    • String *sp = new String;String 클래스의 객체를 동적으로 할당하고 초기화

    • String::operator new(sizeof(String))은 메모리를 할당하는데 사용되는 정적 멤버 함수 operator new를 호출

    • 이 함수는 sizeof(String)만큼의 메모리를 할당하고 그 메모리의 포인터를 반환

    • String::String(sp);은 이전 단계에서 할당한 메모리를 사용하여 String 클래스의 기본 생성자를 호출

    • 이를 통해 객체가 초기화

      /

      배치 new 연산자를 String 클래스에 선언 V

      void *operator new(size_t size, char *memory);
      void *operator new(size_t size, char *memory)
      {
      			return memset(memory, 0, size);
      }

      배치 new를 중복 정의하려면 operator new는 매개변수 두 개 필요

      첫 번째 매개변수 size_t size는 메모리의 크기를 나타내며, 두 번째 매개변수 char* memory는 미리 할당된 메모리 블록

      /

      중복정의 operator new를 다른 버전으로 정의

      String 객체를 가리키는 포인터의 기존 배열에 객체의 주소를 저장

       // 사용:
          String *next(String **pointers, size_t *idx)
          {
              return new(pointers, (*idx)++) String;
          }
      
              // 구현:
          void *String::operator new(size_t size, String **pointers, size_t idx)
          {
              return pointers[idx] = ::operator new(size);
          }

      next 함수 : 인자 pointersString 객체 포인터 배열을 가리키고, idx는 현재 배열에서 다음으로 사용될 인덱스

      next 함수는 새로운 String 객체를 pointers 배열의 idx 위치에 생성하고, 값을 증가

      배열 내의 다음 위치에 새로운 객체가 생성

    • new 연산자는 특정한 메모리 위치에 객체를 생성하고 초기화하는 "placement new" 연산자

    • String::operator new 함수는 String 클래스에서 사용되는 new 연산자의 커스텀 버전

      ::operator new(size)를 호출하여 메모리를 할당하고, 할당된 메모리의 포인터를 pointers 배열의 idx 위치에 저장

      String 객체가 이미 할당된 메모리에 생성되고, pointers 배열에도 해당 객체에 대한 포인터가 저장

      이러한 방식으로 next 함수를 사용하여 String 객체를 생성하면 특정한 위치에 객체를 생성하고 해당 위치를 추적하는 용도로 사용할 수 있습니다. 이것은 예를 들어 메모리 풀을 관리하거나 다른 고급 메모리 관리 기술을 구현하는 데 유용할 수 있습니다.

  • 11.8: `operator delete(void *)' 중복 정의하기 delete연산자도 중복 정의 가능 operator new 중복 정의했다면 operator delete도 중복 정의하는 것을 권장 대응되는 operator delete 를 오버 로딩하지 않으면 메모리가 지정한 크기만큼 할당되지만 해제할 때는 c++의 기본 동작에 따라 처리하기 때문에 할당 로직과 맞지 않을 수 있음 operator deletevoid * 매개변수를 정의해야 함 두 번째 중복 정의 버전은size_t 유형으로 두 번째 매개변수를 정의하는데 operator new[]의 중복 정의와 관련된다. ptr은 String 클래스의 실체를 가리키는 포인터이다. 거기에delete 연산자가 중복 정의되어 있다. 다음 서술문을 간략화한 형태이다.
    ptr->~String(); // 클래스의 소멸자를 호출한다. 
                    // ptr이 가리키는 메모리로 일을 한다.
    String::operator delete(ptr);
    1. ptr->~String();:

      • ptr이 가리키는 객체의 소멸자가 호출되고, 해당 객체의 메모리는 그대로 남아 있음
    2. String::operator delete(ptr);:
      - 객체의 메모리를 직접 해제
      - ptr이 가리키는 메모리 블록을 해제하고, 해당 객체의 소멸자는 호출되지 않음
      - 객체의 메모리를 해제하는 것이 주요 목적

      중복정의된 operator delete를 사용하면 메모리 해제 동작을 원하는 대로 커스터마이징 가능

      void String::operator delete(void *ptr)
      {
      ::delete ptr; // 기본 delete 연산자를 호출 가능
      }
      
      void String::operator delete(void* ptr) {
         logMemoryDeallocation(ptr);
         ::delete(ptr);
          }

      operator delete는 void 매개변수를 사용:

    • operator delete 함수는 항상 void* 매개변수를 받아야 함

      (해제할 메모리 블록의 주소를 가리키는 포인터)
  • 11.9: new[]' 연산자와 delete[]' 연산자
    • operator new와 operator delete처럼 new[]와 delete[]도 중복 정의 가능

    • operator delete[]를 사용하여 이전에 new[]로 배당된 메모리를 삭제

    • 만약 실체가 할당되어 있다면 소멸자가 실체마다 호출

    • 실체를 가리키는 포인터가 할당되어 있다면 그 포인터가 가리키는 실체의 소멸자는 자동으로 호출되지 않음

    • class MyClass {
      public:
      	MyClass() {
      			std::cout << "생성";
      	}
      	~MyClass() {
      	    std::cout << "소멸";
      	}
      
      int main() {
      MyClass* myArray = new MyClass[3]; // MyClass 객체 배열 생성
      delete[] myArray; // 메모리를 삭제하면서 객체의 소멸자 호출
      // 메모리가 해제되었지만, 객체 포인터는 여전히 유효
      myArray = nullptr; // 객체 포인터를 nullptr로 설정하면 소멸자 호출
      }
  • 11.9.1: `new[]' 중복 정의하기  operator new[] 연산자도 추가로 중복 정의 가능
    void* String::operator new[](size_t size, char* memory)
    {
    	return memset(memory, 0, size); // 메모리를 0으로 초기화
    }
    • operator new[] 함수를 중복 정의하여 배열의 메모리를 초기화 memset 함수를 사용하여 메모리를 0으로 초기화하고, 그 메모리의 주소를 반환
  • 11.9.2: `delete[]' 중복 정의하기 (re) String::new[]가 이전에 배당한 메모리 블록의 주소로 매개변수가 초기화된다. operator delete[] 연산자를 구현할 때 주의할 점이 몇 가지 있다. new 연산자와 new[] 연산자는 주소를 돌려준다. 할당된 객체를 이 주소가 가리키고 있지만 그 주소 바로 앞에 있는 size_t 값을 사용할 수 있다. 이 size_t 값은 배당된 블록에 포함되어 있으며 실제 블록의 크기가 담겨 있다. 물론 이것은 배치 new 연산자에는 적용되지 않는다. 클래스가 소멸자를 정의할 때 new[]가 돌려주는 주소 앞의 size_t 값은 배당된 블록의 크기가 아닌 new[]를 호출할 때 지정된 객체의 갯수 보통은 관심의 대상이 아니지만 operator delete[]을 중복정의할 때는 유용한 정보가 될 수 있다. 그런 경우 operator delete[]는 new[]가 돌려준 주소를 받지 않는다. 오히려 최초의 size_t 값의 주소를 받는다. 이것이 유용하지 아닌지는 명확하지 않다. delete[]의 코드가 실행되면 모든 객체가 이미 파괴된 것이다. 그래서 operator delete[]는 얼마나 많은 객체가 소멸했는지 결정하기만 하면 된다. 그러나 그 객체들은 이미 파괴되어 존재하지 않는다. 다음은 operator delete[]의 이 행위를 보여주는 예이다. 최소한의 Demo 클래스이다.
    class Demo {
    public:
        int data;
    
        Demo(int val) : data(val) {}
    };
    
    int main() {
        Demo* arr = new Demo[5]; // -----------------------------------------(1)
        size_t* sizePtr = reinterpret_cast<size_t*>(arr) - 1; //-------------(2)
    
        // 객체 수 출력
        size_t objectCount = *sizePtr;
        std::cout << "Number of objects allocated: " << objectCount << std::endl;
    
        delete[] arr;
        return 0;
    }
    1. new[]로 5개의 Demo 객체 배열을 할당

    2. size_t 값의 위치를 찾음. 포인터를 사용하여 size_t 값을 가져옴. 이 값은 배열의 크기를 나타내며 delete[] 연산자에서 필요한 정보를 제공

      new[]로 배열을 할당하고 이에 대한 포인터를 operator delete[]에 전달하면 size_t 값이 포인터 바로 앞에 존재

      operator delete[] 함수가 몇 개의 객체가 할당되었는지 파악하는 데 사용 가능

      struct Demo
          {
              size_t idx;
              Demo()
              {
                  cout << "default cons\n";
              }
              ~Demo()
              {
                  cout << "destructor\n";
              }
              void *operator new[](size_t size)
              {
                  return ::operator new(size);
              }
              void operator delete[](void *vp)
              {
                  cout << "delete[] for: " << vp << '\n';
                  ::operator delete[](vp);
              }
          };
      
    3. operator new[] 함수 중복 정의

      ::operator new(size)를 호출하여 메모리를 할당하고, 그 메모리 주소를 반환

    4. operator delete[] 함수 중복 정의

      **`::operator delete[](vp)`**를 호출하여 메모리를 해제
          int main()
          {
              Demo *xp;
              cout << ((int *)(xp = new Demo[3]))[-1] << '\n';
              cout << xp << '\n';
              cout << "==================\n";
              delete[] xp;
          }
    5. main 함수에서는 Demo 클래스의 포인터인 xp를 선언

    6. (int *)(xp = new Demo[3]))[-1] 코드는 배열 형태로 Demo 객체 3개를 동적으로 할당 후, 할당된 배열의 주소를 xp에 저장 (배열 크기는 3)

    7. 배열의 주소 바로 앞에 저장된 배열 크기 정보를 읽기 위해 ((int *)(xp = new Demo[3]))[-1]와 같이 포인터 산술을 사용

    8. xp의 주소를 출력

    9. delete[] xp 사용하여 배열 해제

      배열 해제 시 **`operator delete[]`** 함수가 호출되며, 메모리 해제

      이 코드는 배열의 크기 정보를 포인터 산술을 통해 읽어내어 커스텀 operator new[]operator delete[] 함수를 호출하여 메모리 할당 및 해제를 수행하는 예를 보여줍니다.

      String 클래스에 대해 중복 정의 operator delete[] 가 있으므로 자동으로 사용

              delete[] new String[5];

      delete[] 연산자는 size_t 매개변수를 사용해 중복 정의 가능

          void operator delete[](void *p, size_t size);

      여기에서 size는 void *p 가 가리키는 메모리 블록의 크기로 (바이트 단위로) 자동으로 초기화

      이런 형태로 정의되어 있으면 void operator[](void *) 형태는 정의하면 안 됨(모호성 문제)

      operator delete[] 형태의 예:

          void String::operator delete[](void *p, size_t size)
          {
              cout << "deleting " << size << " bytes\n";
              ::operator delete[](ptr);
          }

      더 operator delete[]를 중복 정의할 수도 있지만 그것을 사용하려면 정적 멤버 함수로 명시적으로 호출해야 함

      // 선언:
      void String::operator delete[](void *p, ostream &out);
      // 사용법:
      String *xp = new String[3];
      String::operator delete[](xp, cout);
    10. String 클래스의 operator delete[] 함수는 메모리 블록의 크기를 출력하고, ::operator delete[] 함수를 호출하여 실제 메모리를 해제하는 예제를 보여주었습니다.

    11. 마지막으로, operator delete[]를 별도로 중복 정의하고 사용하려면 정적 멤버 함수로 명시적으로 호출해야 한다는 점을 언급하였으며, 이를 예제를 통해 보여주었습니다.

  • 11.9.3: C++14: `operator delete(void *, size_t)' 가족
    • C++14 표준은 전역적void operator delete(void *, size_t size) 함수와 void operator delete[](void *, size_t size) 함수 중복 정의 지원
    • 크기가 있는 전역적 반납 함수를 사용할 경우, 메모리 반환 작업 더 효율적으로 수행 가능
    • 반환되는 메모리 블록 크기를 미리 알려주므로 할당 및 반환에 대한 최적화 수행 가능
  • 11.9.4: new[]'와 delete[]' 그리고 예외 (안 중요 !!) new[]는 예외에 안전
    • new[]는 필요한 메모리 배당을 시도하는 중에 예외를 던질 경우 > bad_alloc

    • 아무것도 할당되지 않았으므로 메모리 누수 없음

      이미 생성된 객체의 소멸자가 호출되고 객체 자체에 배당된 메모리가 공용 풀에 반납

      생성자가 실패하더라도 기본 보장을 제공

    • 다섯 개의 객체를 할당하고 초기화해 달라고 요청. 그러나 객체를 두 개 생성한 후에 더 이상 생성하지 못하고 예외를 던진다. 출력을 보면 생성된 객체의 소멸자가 적절하게 호출되고 배당된 실제 메모리도 적절하게 반납되고 있음

       #include <iostream>
          using namespace std;
          static size_t count = 0;
      
          class X
          {
              int x;
              public:
                  X()
                  {
                      if (count == 2)
                          throw 1;
                      cout << "Object " << ++count << '\n';
                  }
                  ~X()
                  {
                      cout << "Destroyed " << this << "\n";
                  }
                  void *operator new[](size_t size)
                  {
                      cout << "Allocating objects: " << size << " bytes\n";
                      return ::operator new(size);
                  }
                  void operator delete[](void *mem)
                  {
                      cout << "Deleting memory at " << mem << ", containing: " <<
                          *static_cast<int *>(mem) << "\n";
                      ::operator delete(mem);
                  }
          };
      
          int main()
          try
          {
              X *xp = new X[5];
              cout << "Memory at " << xp << '\n';
              delete[] xp;
          }
          catch (...)
          {
              cout << "Caught exception.\n";
          }

      이 코드는 C++에서 newdelete 연산자를 사용하여 메모리 할당 및 해제를 커스터마이즈하는 방법을 보여주는 예제입니다. 주로 클래스 X를 정의하고 그 안에 연산자를 중복 정의하고 있습니다. 아래는 코드별로 설명합니다.

    1. X 클래스: 클래스 는 생성자, 소멸자 및 new/delete 연산자 중복 정의를 포함합니다.

      • 생성자(X())는 객체가 생성될 때 호출되며, count 변수를 검사하여 객체가 두 번 생성되면 예외를 던집니다(throw 1).
      • 소멸자(~X())는 객체가 파괴될 때 호출되며 객체가 파괴될 때마다 메시지를 출력합니다.
      • operator new[] 함수는 객체 배열을 할당할 때 호출되며 할당된 메모리의 크기를 출력하고, 기본 operator new 함수를 호출하여 메모리를 할당합니다.
      • operator delete[] 함수는 객체 배열을 삭제할 때 호출되며 메모리의 주소와 해당 주소의 값을 출력한 다음, 기본 operator delete 함수를 호출하여 메모리를 반환합니다.
    2. main 함수:
      1. delete[] xp; 문장이 실행되어 xp 포인터가 가리키는 객체 배열이 삭제됩니다. 이때 X 클래스의 소멸자가 호출되고 "Destroyed" 메시지와 객체의 주소가 출력됩니다.
      2. cout << "Memory at " << xp << '\n'; 문장을 통해 xp 포인터가 가리키는 메모리 주소가 출력됩니다.
      3. catch 블록으로 제어가 이동하며 "Caught exception." 메시지가 출력됩니다. 이는 new 연산자 중에 예외가 발생했음을 나타냅니다.

      이 코드는 newdelete 연산자를 중복 정의하고 예외 처리를 통해 오류를 처리하는 방법을 보여주며, 객체의 메모리 할당 및 해제 동작을 사용자 정의할 수 있음을 보여줍니다.

  • 11.10: 함수 객체 (중요 !!)
    • 함수 객체는 operator()가 재정의된 객체
    • 함수 객체는 함수처럼 동작하면서 객체로서 다양한 정보 포함 가능
    • 함수 객체는 주로 진위 함수를 구현하는 데 사용
    • 진위 함수(=진위술어)는 주어진 조건을 만족하는지 판단
    • 진위술어는 단항 진위술어(unary predicate)와 이항 진위술어(binary predicate) 두 가지 종류
    1. 단항 진위술어 (Unary Predicate): 단항 진위술어는 인자로 한 개의 값을 받고, 해당 값을 기반으로 참(true) 또는 거짓(false)을 반환 (ex:주어진 수가 짝수인지)

    2. 이항 진위술어 (Binary Predicate): 이항 진위술어는 인자로 두 개의 값을 받고, 두 값을 비교하여 참 또는 거짓을 반환 (ex:두 숫자를 비교하여 둘 중 더 큰 값)

      Person &target = targetPerson();    // 찾을 사람을 결정한다.
          Person *pArray;
          size_t n = fillPerson(&pArray);
      
          cout << "The target person is";
      
          if (!lsearch(&target, pArray, &n, sizeof(Person), compareFunction))
              cout << " not";
          cout << "found\n";

      Person 클래스와 그 실체의 정렬되지 않은 배열이 있다고 할 때, 배열에서 특정한 Person 실체를 찾으려면 lsearch 함수를 사용

       int compareFunction(void const *p1, void const *p2)
          {
             return *static_cast<Person const *>(p1)!= *static_cast<Person const *>(p2);
          }

      void 포인터로 주어진 두 개의 객체를 Person 객체로 캐스팅한 후, 두 객체를 비교

      <함수 객체와 일반적인 함수 호출 간의 비교>

      일반적인 함수 호출 과정

    3. 비교 함수의 두 인자를 스택에 넣는다.

    4. lsearch 함수의 최종 매개변수 값을 결정해 compareFunction의 주소를 생성한다.

    5. 비교 함수를 호출한다.

    6. 비교 함수 내에서 Person::operator!= 인자의 오른쪽 인자의 주소를 스택에 넣는다.

    7. Person::operator!=를 평가한다.

    8. Person::operator!= 함수를 스택에서 꺼낸다.

    9. 비교 함수의 두 인자를 스택에서 꺼낸다.

      이런 과정에서 함수 호출 및 스택 작업이 많이 발생

      반면, 함수 객체를 사용하면 다음과 같은 이점 존재

    10. 함수 객체는 객체로 취급되므로 생성 및 소멸이 함수 호출보다 빠름

    11. 함수 객체는 상태를 가질 수 있으므로 중간 결과를 재사용하거나 저장 가능

    12. 함수 객체는 함수 호출이 아니라 연산자 호출로 동작하므로 일반적인 함수 호출 오버헤드가 없음

      따라서 함수 객체를 사용하면 코드의 가독성을 높이고 성능 향상 가능

      Person const *PersonSearch(Person *base, size_t nmemb, Person const &target);

      이 함수는 다음과 같이 사용할 수 있다.

        Person const *PersonSearch(Person *base, size_t nmemb,
                                      Person const &target)
          {
              for (int idx = 0; idx < nmemb; ++idx)
                  if (**target**(base[idx]))
                      return base + idx;
              return 0;
          }

      target이 함수 객체로 사용

       bool Person::operator()(Person const &other) const
          {
              return *this == other;
          }

      앞의 괄호는 중복정의 연산자를 정의

      함수 호출 연산자를 중복정의

      operator()가 호출될 때 다음과 같은 일이 일어난다.

    13. Person::operator==의 오른쪽 인자의 주소를 스택에 넣는다.

    14. operator== 함수를 평가한다 (이것도 역시 지정된 목표 객체와 같은 객체를 찾을 때 operator!=를 호출하는 것보다 의미구조적으로 개선된 것이다.).

    15. Person::operator==의 인자를 스택에서 꺼낸다.

      operator()는 인라인 함수이기 때문에 실제로는 호출되지 않고 즉시 operator== 호출

      게다가 필요한 스택 연산은 매우 단순

      간접적으로 호출되는 (즉, 함수를 가리키는 포인터를 사용한) 함수는 절대로 인라인으로 정의 불가 (주소를 알 수 없음)

      간접 호출의 유연성이라는 장점이 수행 부담 때문에 훼손될 수 있으나 인라인 함수객체를 사용하면 프로그램의 효율성을 높일 수 있음

      함수객체는 객체의 비공개 데이터에 접근가능

      (lsearch 함수처럼) 비교 함수가 사용되는 검색 알고리즘에서 처리 대상과 배열 원소는 포인터를 사용하여 비교 함수에 건네지는데, 스택 처리가 덧붙어서 관련된다.

      함수객체를 사용하면 목표로 한 사람은 단일 검색 작업 안에서 바뀌지 않는다. 그러므로 목표로 한 사람을 함수객체의 클래스 생성자에 건넬 수 있다. 실제로 이것이 표현식 target(base[idx]) 안에 일어나는 일이다. 배열의 원소들을 잇따라 하나씩 인자로 받아 검색한다.

  • 11.10.1: 조작자 만들기 <조작자 파트 다시 보기 조작자(Manipulator)는 출력 스트림의 출력을 형식화하고 제어하는 데 사용되는 함수/ 함수 객체 조작자는 << 연산자와 함께 사용되어 출력 스트림에 영향을 미침 주로 출력 데이터를 원하는 형식으로 표시하고 출력을 제어 먼저 조작자의 정의 필요
        std::ostream &w10(std::ostream &str)
        {
            return str << std::setw(10);
        }
    w10 조작자는 출력 필드의 너비를 10으로 설정 std::setw(10) 사용하여 출력 필드의 너비를 10으로 설정한 다음 이 스트림을 반환 이 함수를 사용하려면 ostream 클래스에 대해 << 연산자 중복 정의 중복된 << 연산자 함수는 함수 포인터를 매개변수로 받고 해당 함수를 실행하여 스트림 반환
    extern ostream &w10(ostream &str);
    
    int main()
    {
      w10(cout) << 3 << " ships sailed to America\n";
      cout << "And " << w10 << 3 << " more ships sailed too.\n";
    }

    함수를 조작자로 사용 가능

    w10(cout) << 3 << " ships sailed to America\\n";에서 w10(cout)cout 스트림을 너비 10으로 설정하고 그 뒤에 나오는 3을 출력
    • 인자를 받는 조작자는 매크로로 구현

    • 전처리기로 처리되며 전처리기 단계를 넘어서 사용 불가

    • 연속적인 삽입에 함수를 호출 불가

    • 하나의 서술문에 여러 operator<<연산자가 사용되면 컴파일러는 함수들을 호출하여 반환 값을 절약하며 반환 값을 연이어 삽입에 사용

    • 따라서 << 연산자에 건넨 인자들의 순서가 무효됨

      익명 객체에 기반한 해결책이 존재 (cin이나 cout처럼) 전역적으로 사용 가능한 객체들을 변경에 알맞음

    • 클래스를 생성하고, Align의 생성자는 여러 인자를 받음

    • 예제에서 각각 필드 너비와 정렬 방식을 나타냄

    • 클래스는 중복정의 삽입(추출)도 지원

      ostream &operator<<(ostream &ostr, Align const &align)
    • (익명) 객체가 스트림에 삽입

    • 삽입 연산자는 그 스트림을 Align::align에 건냄

    • 제공된 스트림을 구성해 돌려줄 수 있어 Align 객체를 스트림에 삽입 가능

    • 생성하고 이 객체에 << 연산자를 정의하여, 이 객체에 원하는 출력을 연이어 지정

      cout << "Hello, " << "world!";

      Hello, 출력 후, << 연산자의 반환 값인 cout 객체를 다시 사용해 world!출력

      MyOutput(cout) << "Hello, " << "world!";

      MyOutputcout 객체를 받아서 << 연산자를 재정의한 전역 객체

      객체를 생성하면서 cout을 전달

  • 11.11: [io]fstream::open() 사례 쓰기를 위해 fstream 객체를 열려면 다음과 같이 할 수 있다.
    fstream out;
        out.open("/tmp/out", ios::out);
    읽기와 쓰기로 fstream 객체를 열기 위해 다음 코드가 사용되는 것을 자주 볼 수 있다.
      fstream out;
        out.open("/tmp/out", ios::in | ios::out);
    만든 enum을 사용하여 열거 값을 조합하려고 시도할 때 문제에 봉착할 수 있다.
     enum Permission
        {
            READ =      1 << 0,
            WRITE =     1 << 1,
            EXECUTE =   1 << 2
        };
    
        void setPermission(Permission permission);
    
        int main()
        {
            setPermission(READ | WRITE);
        }
    invalid conversion from 'int' to 'Permission' 'int'로부터 'Permission'으로 불법 변환 ios::openmode 값을 조합한 값들을 스트림의 open 멤버에 건네면 문제가 없는데, 왜 Permission 값을 조합해서 건네면 문제일까? 산술 연산자를 사용하여 열거 값을 조합하면 그 결과 값의 유형은 int 열거 값들을 조합한 결과 값은 여전히 원래의 열거 영역 안에 의미가 있어야 개념적으로 올바르다 간주 READWRITE = READ | WRITE 값을 위의 enum에 추가한 후는 READ | WRITE 값을 setPermission에 인자로 지정 불가 operator<< 같은 자유 함수를 중복정의했고 그런 중복정의 함수는 개념적으로 자신의 클래스의 영역 안에 존재 C++ 언어는 유형이 강력하게 정의되는 언어이기 때문에 열거체(enum)를 정의하는 것은 단순히 int 값을 심볼 이름에 연관짓는 일을 넘어섬(열거 유형은 그 자체로 하나의 유형이기 때문)
  • 11.12: 사용자-정의 기호상수
    • 사용자-정의 기호 상수는 함수로 정의

    • 반드시 이름 공간 영역에 정의

    • 기호 상수 연산자는 클래스의 멤버 함수가 될 수 없음

    • 기호 상수 연산자의 이름은 반드시 밑줄 문자로 시작해야 하며,

    • 기호상수 연산자는 건네야 하는 인자의 뒤에 (밑줄 문자를 포함하여) 이름을 덧붙여 사용

    • _NM2km (nautical mile to km -1.852km))이 기호 상수 연산자의 이름이라고 간주하면

      `100_NM2km`와 같이 호출할 수 있고 185.2 반환

      Type을 사용하여 기호상수 연산자의 반환 유형을 나타내려면 그의 총칭 선언은 다음과 같다.

       Type operator "" _identifier(parameter-list);

      빈 문자열 다음에 빈 공간은 꼭 띄어야 함

      기호상수 연산자를 중복정의할 때 컴파일러는 인자의 데이터 형식과 가장 가까운 중복정의 선택

      정수 120을 인자로 전달하면, C++ 컴파일러는 정수에 가장 일치하는 중복 정의를 찾음

      만약 unsigned long long int 매개변수를 정의한 중복 정의가 있는 경우, 해당 중복 정의를 사용

      그런데 char const *long double 매개변수를 정의한 중복 정의가 존재하는 경우,

      더 정확하게 일치하는 char const * 매개변수를 정의한 중복 정의를 사용

      정수 리터럴인 120은 기본적으로 int 형식으로 간주

      그러나 컴파일러는 이 정수 리터럴을 형변환하여 일치하는 중복 정의를 찾으려고 시도

      컴파일러는 정수를 char const *로 형변환할 수 있으며, long double로도 형변환할 수 있음

      unsigned long long intint보다 더 큰 정수 형식이므로 char const *long double과 비교했을 때는 더 유사하다고 볼 수 있으나 형변환 규칙에 따라 컴파일러는 가능한 한 더 정확한 일치를 찾으려고 노력하며, 이 때 char const *long double과 같이 다른 형식으로 형변환 가능한 경우에는 더 일치하는 것으로 판단

  • 11.13: 중복정의가 가능한 연산자
        +       -       *       /       %       ^       &       |
        ~       !       ,       =       <       >       <=      >=
        ++      --      <<      >>      ==      !=      &&      ||
        +=      -=      *=      /=      %=      ^=      &=      |=
        <<=     >>=     []      ()      ->      ->*     new     new[]
        delete  delete[]
    텍스트형연산자
    and&&
    and_eq&=
    bitand&
    bitor
    compl~
    not!
    not_eq!=
    or
    or_eq
    xor^
    xor_eq^=
    `텍스트형' 연산자도 중복정의할 수 있다. 그렇지만 텍스트형 연산자는 추가 연산자가 아님. 같은 문맥 안에서 operator&& 그리고 operator and를 둘 다 중복 정의 불가 결과적으로 할당 연산자를 전역적으로 재정의하는 것은 불가능  char const *를 lvalue로 받고 String &을 rvalue로 받는 방식 불가 다음 연산자는 중복정의 불가

0개의 댓글