C++/Overloading 2021. 3. 19. 19:32

대입 연산자 오버로딩 (Assignment Operator Overloading)

  • 자기 자신을 assignment할 때(hello = hello 등) 발생할 수 있는 문제를 미리 방지할 수 있다.

  • 깊은 복사를 직접 구현할 수 있다.

    #include <iostream>
    #include <cassert>
    
    class MyString
    {
        char* data_ = nullptr;
        int        len_ = 0;
    
    public:
        MyString(const char* source = "")
        {
            assert(source);
    
            len_ = std::strlen(source) + 1;
            data_ = new char[len_];
    
            for (int i = 0; i < len_; ++i)
                data_[i] = source[i];
            data_[len_ - 1] = '\0';
        }
    
        ~MyString()
        {
            delete[] data_;
        }
    
        MyString(const MyString& source)
        {
            std::cout << "Copy constructor\n";
    
            len_ = source.len_;
    
            if (source.data_ != nullptr)
            {
                data_ = new char[len_];
    
                for (int i = 0; i < len_; ++i)
                    data_[i] = source.data_[i];
            }
            else
                data_ = nullptr;
        }
    
        char*& getString() { return data_; }
    
        MyString& operator = (const MyString& source)
        {
            std::cout << "Assignment operator\n";
    
            if (this == &source)  // prevent self-assignment
                return *this;
    
            delete[] data_;
    
            len_ = source.len_;
    
            if (source.data_ != nullptr)
            {
                data_ = new char[len_];
    
                for (int i = 0; i < len_; ++i)
                    data_[i] = source.data_[i];
            }
            else
                data_ = nullptr;
        }
    };
    
    int        main()
    {
        using namespace std;
    
        MyString    hello("Hello");
    
        cout << (int*)hello.getString() << endl;
        cout << hello.getString() << endl;
    
        {
            MyString    copy;
    
            copy = hello;
    
            cout << (int*)copy.getString() << endl;
            cout << copy.getString() << endl;
        }
    
        cout << hello.getString() << endl;
    }
    
    /* stdout
    00C0EAD8
    Hello
    Assignment operator
    00C0E5D0
    Hello
    Hello
    */
  • 같은 자료형을 인자로 넣는 초기화는 Copy Constructor를 호출한다.

    • 대입 연산자를 통해 값을 넣으려면 초기화가 아닌 대입을 해야한다.

      #include <iostream>
      #include <cassert>
      
      class MyString
      {
          char* data_ = nullptr;
          int        len_ = 0;
      
      public:
          MyString(const char* source = "")
          {
              assert(source);
      
              len_ = std::strlen(source) + 1;
              data_ = new char[len_];
      
              for (int i = 0; i < len_; ++i)
                  data_[i] = source[i];
              data_[len_ - 1] = '\0';
          }
      
          ~MyString()
          {
              delete[] data_;
          }
      
          MyString(const MyString& source)
          {
              std::cout << "Copy constructor\n";
      
              len_ = source.len_;
      
              if (source.data_ != nullptr)
              {
                  data_ = new char[len_];
      
                  for (int i = 0; i < len_; ++i)
                      data_[i] = source.data_[i];
              }
              else
                  data_ = nullptr;
          }
      
          char*& getString() { return data_; }
      
          MyString& operator = (const MyString& source)
          {
              std::cout << "Assignment operator\n";
      
              if (this == &source)  // prevent self-assignment
                  return *this;
      
              delete[] data_;
      
              len_ = source.len_;
      
              if (source.data_ != nullptr)
              {
                  data_ = new char[len_];
      
                  for (int i = 0; i < len_; ++i)
                      data_[i] = source.data_[i];
              }
              else
                  data_ = nullptr;
          }
      };
      
      int        main()
      {
          using namespace std;
      
          MyString    hello("Hello");
      
          MyString    str1 = hello;
      
          MyString    str2(hello);
      
          MyString    str3{ hello };
      
          MyString    str4;
      
          str4 = hello;
      }
      
      /* stdout
      Copy constructor
      Copy constructor
      Copy constructor
      Assignment operator
      */
C++/Overloading 2021. 3. 16. 00:29

형변환 오버로딩 (Typecasts Overloading)

  • (int)객체, int(객체), static_cast<int>(객체) 등의 형태로 형변환을 할 수 있게 해준다.

  • 묵시적 형변환도 가능해진다.


예제

  #include <iostream>

  class Cents
  {
    int cents_;

  public:
    Cents(int cents = 0) { cents_ = cents; }

    operator int()
    {
      std::cout << "cast here" << std::endl;
      return cents_;
    }
  };

  void    printInt(const int& value)
  {
    std::cout << value << std::endl;
  }

  int        main()
  {
    using namespace std;

    Cents    cents(7);

    printInt(cents);

    int value;

    value = (int)cents;
    value = int(cents);
    value = static_cast<int>(cents);
  }

  /* stdout
  cast here
  7
  cast here
  cast here
  cast here
  */
  • 다른 객체로의 형변환 오버로딩도 가능하다.

    #include <iostream>
    
    class Cents
    {
      int cents_ = 0;
    
    public:
      Cents(const int& cents = 0) { cents_ = cents; }
    
      operator int()
      {
        std::cout << "cast here" << std::endl;
        return cents_;
      }
    };
    
    class Dollars
    {
      int    dollars_ = 0;
    
    public:
      Dollars(const int& dollars = 0) { dollars_ = dollars; }
    
      operator Cents()
      {
        return Cents(dollars_ * 100);
      }
    };
    
    void    printInt(const int& value)
    {
      std::cout << value << std::endl;
    }
    
    int        main()
    {
      using namespace std;
    
      Dollars    dol(2);
      Cents    cents(dol);
    
      printInt(cents);
    }
    
    /* stdout
    cast here
    200
    */
C++/Overloading 2021. 3. 16. 00:27

괄호 연산자 오버로딩 (Parenthesis Operator Overloading)

  • ()(parenthesis)

  • Functor

    • 객체를 함수처럼 사용하는 것

예제

  #include <iostream>

  class Accumulator
  {
    int counter_ = 0;

  public:
    int operator() (int i) { return (counter_ += i); }
  };

  int        main()
  {
    using namespace std;

    Accumulator acc;

    cout << acc(10) << endl;
    cout << acc(20) << endl;
  }

  /* stdout
  10
  30
  */
C++/Overloading 2021. 3. 16. 00:26

첨자 연산자 오버로딩 (Subscript Operator Overloading)

  • [](subscript operator) 내부에 숫자 뿐만 아니라 문자 등 다양한 자료형이 들어갈 수 있다.

예제

  #include <iostream>

  class IntList
  {
    int    list_[10];

  public:
    int& operator [] (const int index)
    {
      return list_[index];
    }
  };

  int        main()
  {
    using namespace std;

    IntList my_list;
    my_list[3] = 1;
    cout << my_list[3] << endl;
    my_list[3] = 10;
    cout << my_list[3] << endl;
  }

  /* stdout
  1
  10
  */
  • const를 사용할 경우

    #include <iostream>
    
    class IntList
    {
      int    list_[10] = { 0, 1, 2, 3, 4, 5 };
    
    public:
      int& operator [] (const int index)
      {
        return list_[index];
      }
    
      const int& operator [] (const int index) const
      {
        return list_[index];
      }
    };
    
    int        main()
    {
      using namespace std;
    
      const IntList my_list;
    
      cout << my_list[3] << endl;
    }
    
    /* stdout
    3
    */
C++/Overloading 2021. 3. 16. 00:25

증감 연산자 오버로딩 (Increment and Decrement Operator Overloading)

  • ++ 연산자 오버로딩 예제

    • 전위(prefix), 후위(postfix)에 따라 오버로딩 방식이 다르다.

      • 클래스 내에서 정의하는 연산자 오버로딩은 파라미터를 받지 않으면 단항 연산자처럼 앞에 붙는 방식으로 오버로딩된다. (전위)

      • 따라서 후위 연산자를 오버로딩하려면 int 자료형의 더미 변수가 있어야 한다.

      #include <iostream>
      
      class Digit
      {
        int    digit_;
      
      public:
        Digit(const int& digit) { digit_ = digit; }
      
        // prefix
        Digit& operator ++ ()
        {
          ++digit_;
          return (*this);
        }
      
        // postfix
        Digit operator ++ (int)
        {
          Digit temp(digit_);
          ++(*this);
          return (temp);
        }
      
        friend std::ostream& operator << (std::ostream& out, const Digit& d)
        {
          out << d.digit_;
          return (out);
        }
      };
      
      int        main()
      {
        using namespace std;
      
        Digit    d(5);
      
        cout << d << endl;
        cout << ++d << endl;
        cout << d++ << endl;
        cout << d << endl;
      }
      
      /* stdout
      5
      6
      6
      7
      */
      • int 대신 다른 자료형으로 바꿔보면 다음과 같은 오류가 발생한다.

        error C2807: the second formal parameter to postfix 'operator ++' must be 'int'
C++/Overloading 2021. 3. 16. 00:25

비교 연산자 오버로딩 (Comparison Operator Overloading)

  • if문, std::sort 등을 사용하려면 필수적으로 구현해야 한다.

예제

  • ==, != 오버로딩 예제

    #include <iostream>
    
    class Cents
    {
      int    cents_;
    
    public:
      Cents(int cents = 0) { cents_ = cents; }
    
      bool operator == (Cents& c)
      {
        return (cents_ == c.cents_);
      }
    
      bool operator != (Cents& c)
      {
        return (cents_ != c.cents_);
      }
    
      friend std::ostream& operator << (std::ostream& out, const Cents& cents)
      {
        out << cents.cents_;
        return (out);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Cents    c1{ 6 };
      Cents    c2{ 6 };
      Cents    c3{ 0 };
    
      cout << std::boolalpha;
      cout << (c1 == c2) << endl;
      cout << (c1 != c2) << endl;
      cout << (c1 == c3) << endl;
      cout << (c1 != c3) << endl;
    }
    
    /* stdout
    true
    false
    false
    true
    */
  • < 오버로딩 예제

    • std::sort 함수를 사용하려면 < 연산자를 오버로딩해야 한다.
    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    class Cents
    {
      int    cents_;
    
    public:
      Cents(int cents = 0) { cents_ = cents; }
      int& getCents() { return cents_; }
    
      bool    operator < (const Cents& c)
      {
        return (cents_ < c.cents_);
      }
    
      friend std::ostream& operator << (std::ostream& out, const Cents& cents)
      {
        out << cents.cents_;
        return (out);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      vector<Cents>    arr(20);
    
      for (size_t i = 0; i < 20; ++i)
        arr[i].getCents() = i;
    
      std::random_shuffle(begin(arr), end(arr));
    
      for (auto& e : arr)
        cout << e << ' ';
      cout << endl;
    
      std::sort(begin(arr), end(arr));
    
      for (auto& e : arr)
        cout << e << ' ';
      cout << endl;
    }
    
    /* stdout
    12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
    */
C++/Overloading 2021. 3. 16. 00:24

단항 연산자 오버로딩 (Unary Operator Overloading)

  • -, ! 오버로딩

    #include <iostream>
    
    class Cents
    {
      int    cents_;
    
    public:
      Cents(int cents = 0) { cents_ = cents; }
    
      Cents operator - () const
      {
        return Cents(-cents_);
      }
    
      bool operator ! () const
      {
        return (cents_ == 0) ? true : false;
      }
    
      friend std::ostream& operator << (std::ostream& out, const Cents& cents)
      {
        out << cents.cents_;
        return (out);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Cents    c1{ 6 };
      Cents    c2{ 0 };
    
      cout << -c1 << ' ' << -c2 << endl;
      cout << std::boolalpha;
      cout << !c1 << ' ' << !c2 << endl;
    }
    
    /* stdout
    -6 0
    false true
    */
C++/Overloading 2021. 3. 16. 00:20

입출력 연산자 오버로딩 (I/O Operator Overloading)

  • 멤버 함수로 만들 수 없다.

    • 첫 번째 인자가 stream이기 때문이다.

예제

  • friend를 사용한 출력 연산자 오버로딩 예제

    #include <iostream>
    
    class Point
    {
      double    x_;
      double    y_;
      double    z_;
    
    public:
      Point(double x = 0.0, double y = 0.0, double z = 0.0)
        : x_(x), y_(y), z_(z)
      {}
    
      friend std::ostream& operator << (std::ostream& out, const Point& p)
      {
        out << '(' << p.x_ << ", " << p.y_ << ", " << p.z_ << ')';
        return (out);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
    
      cout << p1 << ' ' << p2 << endl;
    }
    
    /* stdout
    (0, 0.1, 0.2) (3.4, 1.5, 2)
    */
  • stream을 통해 파일 입출력을 할 경우, 이를 이용해서 굉장히 쉽게 할 수 있다.

    • 출력되는 파일은 VS기준 왼쪽 위의 파일명을 우클릭해서 나오는 Open Containing folder를 클릭하면 열리는 폴더에 있다.
      • 단축키는 Ctrl + K를 누른 다음 R을 누르면 된다. (Ctrl + R이 아니다.)
    #include <iostream>
    #include <fstream>
    
    class Point
    {
      double    x_;
      double    y_;
      double    z_;
    
    public:
      Point(double x = 0.0, double y = 0.0, double z = 0.0)
        : x_(x), y_(y), z_(z)
      {}
    
      friend std::ostream& operator << (std::ostream& out, const Point& p)
      {
        out << '(' << p.x_ << ", " << p.y_ << ", " << p.z_ << ')';
        return (out);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Point p1(0.0, 0.1, 0.2), p2(3.4, 1.5, 2.0);
    
      ofstream of("out.txt");
    
      of << p1 << ' ' << p2 << endl;
    
      of.close();
    }
    
    /* out.txt
    (0, 0.1, 0.2) (3.4, 1.5, 2)
    */
  • friend를 사용한 입력 연산자 오버로딩 예제

    #include <iostream>
    
    class Point
    {
      double    x_;
      double    y_;
      double    z_;
    
    public:
      Point(double x = 0.0, double y = 0.0, double z = 0.0)
        : x_(x), y_(y), z_(z)
      {}
    
      friend std::ostream& operator << (std::ostream& out, const Point& p)
      {
        out << '(' << p.x_ << ", " << p.y_ << ", " << p.z_ << ')';
        return (out);
      }
    
      friend std::istream& operator >> (std::istream& in, Point& p)
      {
        in >> p.x_ >> p.y_ >> p.z_;
        return (in);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Point p1, p2;
    
      cin >> p1 >> p2;
    
      cout << p1 << ' ' << p2 << endl;
    }
    
    /* stdin
    1 2 3 4 5 6
    */
    
    /* stdout
    (1, 2, 3) (4, 5, 6)
    */
C++/Overloading 2021. 3. 16. 00:19

산술 연산자 오버로딩 (Arithmetic Operator Overloading)

  • 클래스 안에서 정의해도 되고, 밖에서 정의해도 된다.

    • 안에서 정의할 때는 this로 첫 번째 파라미터를 받게되므로 파라미터가 하나 줄어든다.

    • 밖에서 정의할 때는 클래스에서 friend 키워드로 선언해주는게 편할 수도 있다.


예제

  • 오버로딩을 하지 않은 예제

    #include <iostream>
    
    class Cents
    {
      int    cents_;
    
    public:
      Cents(const int& cents = 0) { cents_ = cents; }
      int        getCents() const { return cents_; }
      int&    getCents() { return cents_; }
    };
    
    Cents    add(const Cents& c1, const Cents& c2)
    {
      return Cents(c1.getCents() + c2.getCents());
    }
    
    int        main()
    {
      using namespace std;
    
      Cents cents1(6);
      Cents cents2(8);
    
      cout << add(cents1, cents2).getCents() << endl;
    }
    
    /* stdout
    14
    */
  • + 연산자 오버로딩을 하면 다음과 같다.

    #include <iostream>
    
    class Cents
    {
      int    cents_;
    
    public:
      Cents(const int& cents = 0) { cents_ = cents; }
      int        getCents() const { return cents_; }
      int&    getCents() { return cents_; }
    };
    
    Cents    operator + (const Cents& c1, const Cents& c2)
    {
      return Cents(c1.getCents() + c2.getCents());
    }
    
    int        main()
    {
      using namespace std;
    
      Cents cents1(6);
      Cents cents2(8);
    
      cout << (cents1 + cents2 + Cents(6)).getCents() << endl;
    }
    
    /* stdout
    20
    */
  • 클래스 안에서 정의하면 다음과 같다.

    #include <iostream>
    
    class Cents
    {
      int    cents_;
    
    public:
      Cents(const int& cents = 0) { cents_ = cents; }
      int        getCents() const { return cents_; }
      int&    getCents() { return cents_; }
    
      Cents    operator + (const Cents& c2)
      {
        return Cents(cents_ + c2.cents_);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Cents cents1(6);
      Cents cents2(8);
    
      cout << (cents1 + cents2 + Cents(16)).getCents() << endl;
    }
    
    /* stdout
    30
    */
C++/Overloading 2021. 3. 16. 00:16

오버로딩 (Overloading)

산술 연산자 오버로딩 (Arithmetic Operator Overloading)

입출력 연산자 오버로딩 (I/O Operator Overloading)

단항 연산자 오버로딩 (Unary Operator Overloading)

비교 연산자 오버로딩 (Comparison Operator Overloading)

증감 연산자 오버로딩 (Increment and Decrement Operator Overloading)

첨자 연산자 오버로딩 (Subscript Operator Overloading)

괄호 연산자 오버로딩 (Parenthesis Operator Overloading)

형변환 오버로딩 (Typecasts Overloading)

대입 연산자 오버로딩 (Assignment Operator Overloading)


주의사항

  • 클래스 안에서 멤버로만 오버로딩할 수 있는 연산자

    • =(assignment), [](subscript), ()(parenthesis), ->(member access through pointer)
  • 오버로딩이 불가능한 연산자

    • ?:(삼항 연산자), ::, sizeof, ., .*
  • 오버로딩을 해도 연산자 우선순위는 변하지 않는다.

    • ^의 경우 우선순위가 매우 낮은 편이기 때문에, 사용하려면 괄호로 감싸는게 안전하다.