C++/Templates 2021. 3. 22. 00:07

멤버 함수를 한 번 더 템플릿화하기

  • 클래스 템플릿에 있는 멤버 함수를 또 다시 템플릿화할 수 있다.

예제

  #include <iostream>

  using namespace std;

  template <typename T>
  class A
  {
    T    value_;

  public:
    A(const T & input)
      : value_(input)
    {}

    template <typename TT>
    void doSomething()
    {
      cout << typeid(T).name() << " " << typeid(TT).name() << endl;
    }

    void print()
    {
      cout << value_ << endl;
    }
  };

  int            main()
  {
    A<int> a_int(123);
    a_int.print();
    a_int.doSomething<float>();
  }

  /* stdout
  123
  int float
  */

응용

  • char 타입을 int 타입으로 형변환하여 출력하기

    • 함수 템플릿은 함수명 옆에 <int>를 명시하는 대신 인자로 int 타입의 값을 넣으면 파라미터가 알아서 들어간다.
    #include <iostream>
    
    using namespace std;
    
    template <typename T>
    class A
    {
      T    value_;
    
    public:
      A(const T & input)
        : value_(input)
      {}
    
      template <typename TT>
      void doSomething(const TT& input)
      {
        cout << typeid(T).name() << " to " << typeid(TT).name() << endl;
        cout << (TT)value_ << endl;
      }
    
      void print()
      {
        cout << value_ << endl;
      }
    };
    
    int            main()
    {
      A<char> a_char('A');
      a_char.print();
      a_char.doSomething(int());
    }
    
    /* stdout
    A
    char to int
    65
    */
C++/Templates 2021. 3. 22. 00:05

포인터 템플릿 특수화(Pointer Templates Specialization)

  • 템플릿 파라미터가 포인터인 경우 특수화를 하고 싶다면 클래스명에 <T*>를 붙여서 작성한다.

예제

  #include <iostream>

  using namespace std;

  template <typename T>
  class A
  {
    T    value_;

  public:
    A(const T & input)
      : value_(input)
    {}

    void print()
    {
      cout << value_ << endl;
    }
  };

  template <typename T>
  class A<T*>
  {
    T*    value_;

  public:
    A(T* input)
      : value_(input)
    {}

    void print()
    {
      cout << *value_ << endl;
    }
  };

  int            main()
  {
    A<int> a_int(123);
    a_int.print();

    int tmp = 456;

    A<int*> a_int_ptr(&tmp);
    a_int_ptr.print();

    double tmp_d = 3.141592;
    A<double*> a_double_ptr(&tmp_d);
    a_double_ptr.print();
  }

  /* stdout
  123
  456
  3.14159
  */
C++/Templates 2021. 3. 22. 00:04

템플릿 부분 특수화 (Templates Partial Specialization)

  • 템플릿에서 여러 파라미터를 받을 때, 특정 파라미터만 특수화할 수 있다.

예제

일반 함수 템플릿 부분 특수화

  #include <iostream>

  using namespace std;

  template <typename T, int size>
  class StaticArray
  {
  private:
    T    array_[size];

  public:
    T*    getArray() { return array_; }
    T&    operator[](int index)
    {
      return array_[index];
    }
  };

  template <typename T, int size>
  void        print(StaticArray<T, size>& array)
  {
    for (int count = 0; count < size; ++count)
      cout << array[count] << ' ';
    cout << endl;
  }

  template <int size>
  void        print(StaticArray<char, size>& array)
  {
    for (int count = 0; count < size; ++count)
      cout << array[count];
    cout << endl;
  }

  int            main()
  {
    StaticArray<int, 4>    int4;

    int4[0] = 1;
    int4[1] = 2;
    int4[2] = 3;
    int4[3] = 4;

    print(int4);

    StaticArray<char, 14> char14;

    strcpy_s(char14.getArray(), 14, "Hello, World!");

    print(char14);
  }

  /* stdout
  1 2 3 4
  Hello, World!
  */

템플릿 클래스의 멤버 함수 부분 특수화

  • 상속을 이용한다.

    • 기존의 클래스 뒤에 _BASE를 붙이고, 이를 상속하는 클래스를 원래 이름으로 만든다.

    • 클래스 템플릿 특수화를 사용하여 특수화를 하고 싶은 부분만 구현해주면 된다.

    #include <iostream>
    
    using namespace std;
    
    template <typename T, int size>
    class StaticArray_BASE
    {
    private:
      T    array_[size];
    
    public:
      T*    getArray() { return array_; }
      T&    operator[](int index)
      {
        return array_[index];
      }
    
      void    print()
      {
        for (int count = 0; count < size; ++count)
          cout << (*this)[count] << ' ';
        cout << endl;
      }
    };
    
    template <typename T, int size>
    class StaticArray : public StaticArray_BASE<T, size>
    {
    };
    
    template <int size>
    class StaticArray<char, size> : public StaticArray_BASE<char, size>
    {
    public:
      void    print()
      {
        for (int count = 0; count < size; ++count)
          cout << (*this)[count];
        cout << endl;
      }
    };
    
    int            main()
    {
      StaticArray<int, 4>    int4;
    
      int4[0] = 1;
      int4[1] = 2;
      int4[2] = 3;
      int4[3] = 4;
    
      int4.print();
    
      StaticArray<char, 14> char14;
    
      strcpy_s(char14.getArray(), 14, "Hello, World!");
    
      char14.print();
    }
    
    /* stdout
    1 2 3 4
    Hello, World!
    */
C++/Templates 2021. 3. 22. 00:02

클래스 템플릿 특수화 (Class Templates Specialization)

  • 템플릿의 특정 자료형에 대해서 다르게 처리하고 싶을 때 사용한다.

  • 함수 템플릿 특수화에서는 template<>을 명시하지 않아도 작동이 됐는데, 클래스 템플릿 특수화에서는 명시하지 않으면 오류가 발생한다.

    error C2906: 'A<char>': explicit specialization requires 'template <>'

예제

  • 클래스 템플릿 특수화는 다른 클래스를 하나 따로 만들었다고 생각해야 한다.

    • 아래의 예제에서 a_char.test 함수는 존재하지 않는다. 상속과는 엄연히 다르다.
    #include "MyArray.h"
    
    using namespace std;
    
    template<typename T>
    class A
    {
    public:
      void doSomething()
      {
        cout << typeid(T).name() << endl;
      }
    
      void test() {}
    };
    
    template<>
    class A<char>
    {
    public:
      void doSomething()
      {
        cout << "Char type specialization\n";
      }
    };
    
    int            main()
    {
      A<int>        a_int;
      A<double>    a_double;
      A<char>        a_char;
    
      a_int.doSomething();
      a_double.doSomething();
      a_char.doSomething();
    }
    
    /* stdout
    int
    double
    Char type specialization
    */

비트마스크를 통한 메모리 절약 예제

  • 아래 예제에서 bool 자료형에 대해 클래스 템플릿 특수화를 하지 않는다면, Storage8 인스턴스는 8바이트의 크기를 차지하게 된다.

  • 대신 bool 자료형일 경우 비트마스크를 이용하여 메모리를 1/8 수준으로 절약할 수 있다.

    #include <iostream>
    
    using namespace std;
    
    template<typename T>
    class Storage8
    {
      T    array_[8];
    
    public:
      void        set(int index, const T& value)
      {
        array_[index] = value;
      }
    
      const T&    get(int index)
      {
        return array_[index];
      }
    };
    
    template<>
    class Storage8<bool>
    {
      unsigned char    data_;
    
    public:
      Storage8() : data_(0)
      {}
    
      void    set(int index, bool value)
      {
        unsigned char mask = 1 << index;
    
        if (value)
          data_ |= mask;
        else
          data_ &= ~mask;
      }
    
      bool    get(int index)
      {
        unsigned char mask = 1 << index;
        return ((data_ & mask) != 0);
      }
    };
    
    int            main()
    {
      Storage8<int> intStorage;
    
      for (int count = 0; count < 8; ++count)
        intStorage.set(count, count);
      for (int count = 0; count < 8; ++count)
        cout << intStorage.get(count) << '\n';
      cout << "Sizeof Storage8<int> : " << sizeof(Storage8<int>) << endl;
    
      Storage8<bool> boolStorage;
    
      for (int count = 0; count < 8; ++count)
        boolStorage.set(count, count & 3);
      for (int count = 0; count < 8; ++count)
        cout << std::boolalpha << boolStorage.get(count) << '\n';
      cout << "Sizeof Storage8<bool> : " << sizeof(Storage8<bool>) << endl;
    }
    
    /* stdout
    0
    1
    2
    3
    4
    5
    6
    7
    Sizeof Storage8<int> : 32
    false
    true
    true
    true
    false
    true
    true
    true
    Sizeof Storage8<bool> : 1
    */
C++/Templates 2021. 3. 22. 00:00

함수 템플릿 특수화 (Function Templates Specialization)

  • 템플릿이 있는 상태에서, 특정 자료형에 대해 다른 코드를 실행하고 싶을 때 사용한다.

  • explicit instantiation과 비슷한 느낌인데, 이렇게 정의된 함수는 템플릿에서 따로 인스턴스화하지 않는다고 생각하면 될 것 같다.


예제

  #include "MyArray.h"

  using namespace std;

  template<typename T>
  T            getMax(T x, T y)
  {
    return (x > y) ? x : y;
  }

  template<>
  char        getMax(char x, char y)
  {
    cout << "Warning : comparing chars\n";
    return (x > y) ? x : y;
  }

  int            main()
  {
    cout << getMax(1, 2) << endl;
    cout << getMax('a', 'b') << endl;
  }

  /* stdout
  2
  Warning : comparing chars
  b
  */

  • 템플릿 특수화 코드는 보통 헤더에 같이 집어넣는 듯 하다.

    • .cpp 파일로 분리하면 적용하기가 번거롭기 때문이다.
  • 앞에 template<> 키워드는 없어도 작동이 되는데, 템플릿 특수화를 표시하기 위해 쓰는 것 같다.

C++/Templates 2021. 3. 21. 23:59

자료형이 아닌 템플릿 매개변수 (Non-type Templates Parameters)

  • 템플릿 매개변수는 컴파일 타임에 결정되어야 한다.

    • 템플릿의 원리가 여러 경우에 대해 컴파일을 따로 해주는 것이기 때문이다.
  • Non-type 파라미터를 사용할 경우 헤더에서 함수를 정의한다.

    • template<typename T, unsigned int T_SIZE>와 같이 unsigned int가 온다고 생각해보자.

    • 모든 숫자에 대해 explicit instantiation을 적용할 수는 없으므로 헤더에서 정의하는 것이다.


예제

main.cpp

  #include "MyArray.h"

  int            main()
  {
    MyArray<double, 10> my_array;

    for (int i = 0; i < my_array.getLength(); ++i)
      my_array[i] = i * 0.5;

    my_array.print();
  }

  /* stdout
  0 0.5 1 1.5 2 2.5 3 3.5 4 4.5
  */

MyArray.h

  #pragma once
  #include <iostream>
  #include <cassert>

  template<typename T, unsigned int T_SIZE>
  class MyArray
  {
    T*        data_;

  public:
    MyArray()
    {
      data_ = new T[T_SIZE];
    }

    ~MyArray()
    {
      delete[] data_;
    }

    T& operator[](int index)
    {
      assert(index >= 0 && index < T_SIZE);
      return data_[index];
    }

    int getLength()
    {
      return T_SIZE;
    }

    void print()
    {
      for (int i = 0; i < T_SIZE; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }
  };
C++/Templates 2021. 3. 21. 23:59

클래스 템플릿 (Class Templates)

  • 클래스에도 템플릿을 적용할 수 있다.

예제

  • 템플릿을 사용하지 않은 예제

    main.cpp

    #include "MyArray.h"
    
    int            main()
    {
      MyArray my_array(10);
    
      for (int i = 0; i < my_array.getLength(); ++i)
        my_array[i] = i * 10;
    
      my_array.print();
    }
    
    /* stdout
    0 10 20 30 40 50 60 70 80 90
    */

    MyArray.h

    #pragma once
    #include <iostream>
    #include <cassert>
    
    class MyArray
    {
      int  length_;
      int* data_;
    
    public:
      MyArray()
      {
        length_ = 0;
        data_ = nullptr;
      }
    
      MyArray(const int& length_in)
      {
        data_ = new int[length_in];
        length_ = length_in;
      }
    
      ~MyArray()
      {
        reset();
      }
    
      void reset()
      {
        delete[] data_;
        data_ = nullptr;
        length_ = 0;
      }
    
      int& operator[](int index)
      {
        assert(index >= 0 && index < length_);
        return data_[index];
      }
    
      int getLength()
      {
        return length_;
      }
    
      void print()
      {
        for (int i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
    };

  • 템플릿을 적용한 예제

    main.cpp

    #include "MyArray.h"
    
    int            main()
    {
      MyArray<int> my_array(10);
    
      for (int i = 0; i < my_array.getLength(); ++i)
        my_array[i] = i * 10;
    
      my_array.print();
    }
    
    /* stdout
    0 10 20 30 40 50 60 70 80 90
    */

    MyArray.h

    #pragma once
    #include <iostream>
    #include <cassert>
    
    template<typename T>
    class MyArray
    {
      int        length_;
      T*        data_;
    
    public:
      MyArray()
      {
        length_ = 0;
        data_ = nullptr;
      }
    
      MyArray(const int& length_in)
      {
        data_ = new T[length_in];
        length_ = length_in;
      }
    
      ~MyArray()
      {
        reset();
      }
    
      void reset()
      {
        delete[] data_;
        data_ = nullptr;
        length_ = 0;
      }
    
      T& operator[](int index)
      {
        assert(index >= 0 && index < length_);
        return data_[index];
      }
    
      int getLength()
      {
        return length_;
      }
    
      void print()
      {
        for (int i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
    };
    • main.cpp 에서 다른 자료형을 넣어도 작동한다.

      #include "MyArray.h"
      
      int            main()
      {
        MyArray<double> my_array(10);
      
        for (int i = 0; i < my_array.getLength(); ++i)
          my_array[i] = i * 0.5;
      
        my_array.print();
      }
      
      /* stdout
      0 0.5 1 1.5 2 2.5 3 3.5 4 4.5
      */
      #include "MyArray.h"
      
      int            main()
      {
        MyArray<char> my_array(10);
      
        for (int i = 0; i < my_array.getLength(); ++i)
          my_array[i] = i + 65;
      
        my_array.print();
      }
      
      /* stdout
      A B C D E F G H I J
      */

헤더 파일과 .cpp 파일의 분리

  • 헤더에 함수를 정의했는데, 이를 .cpp 파일로 분리해야 한다.

  • 템플릿을 사용하면 이 과정이 살짝 번거롭다.

  • 일단 헤더 파일에서만 print 함수를 분리해보면 다음과 같다.

    MyArray.h

    #pragma once
    #include <iostream>
    #include <cassert>
    
    template<typename T>
    class MyArray
    {
      int        length_;
      T*        data_;
    
    public:
      MyArray()
      {
        length_ = 0;
        data_ = nullptr;
      }
    
      MyArray(const int& length_in)
      {
        data_ = new T[length_in];
        length_ = length_in;
      }
    
      ~MyArray()
      {
        reset();
      }
    
      void reset()
      {
        delete[] data_;
        data_ = nullptr;
        length_ = 0;
      }
    
      T& operator[](int index)
      {
        assert(index >= 0 && index < length_);
        return data_[index];
      }
    
      int getLength()
      {
        return length_;
      }
    
      void print();
    };
    
    template<typename T>
    void MyArray<T>::print()
    {
      for (int i = 0; i < length_; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }
    • 이대로 컴파일하면 아무 문제가 없다.
  • 다음은 MyArray.cpp 파일로 옮기는 과정이다.

    • 헤더 파일에서 분리한 함수 정의 부분을 잘라내서, .cpp 파일에 그대로 붙여넣기한다.

      MyArray.cpp

      #include "MyArray.h"
      
      template<typename T>
      void MyArray<T>::print()
      {
        for (int i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
      error LNK2019: unresolved external symbol "public: void __cdecl MyArray<char>::print(void)" (?print@?$MyArray@D@@QEAAXXZ) referenced in function main
      • main.cpp에서 MyArray.cpp에 정의된 함수의 body 내용을 알 수 없기 때문에 발생하는 문제라고 한다.

        • 템플릿의 작동 원리를 알아야 완벽하게 파악할 수 있을 것 같다.
      • main.cpp에서 MyArray.cppinclude 하면 해결되긴 하는데, 이렇게 하는 것은 좋지 않다고 한다.

      • 대신 explicit instantiation을 해야하는데, 명시적으로 템플릿을 적용하는 것이다.


Explicit Instantiation

  • 클래스나 함수에 적용할 템플릿을 명시해주는 것이다.

    MyArray.cpp

    #include "MyArray.h"
    
    template<typename T>
    void MyArray<T>::print()
    {
      for (int i = 0; i < length_; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }
    
    template class MyArray<char>;
    template void MyArray<double>::print();
C++/Templates 2021. 3. 21. 23:58

함수 템플릿 (Function Templates)

  • template<typename T>, template<class T>의 형태로 템플릿을 선언할 수 있다.

예제

  #include <iostream>

  template<typename T>
  T    getMax(T x, T y)
  {
    return (x > y) ? x : y;
  }

  class Cents
  {
      int    cents_;

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

      bool    operator > (const Cents& c)
      {
          return (cents_ > c.cents_);
      }

      friend std::ostream& operator << (std::ostream& out, const Cents& cents)
      {
          out << cents.cents_ << " cents";
          return (out);
      }
  };

  int        main()
  {
    using namespace std;

    cout << getMax(1, 2) << endl;
    cout << getMax(3.14, 1.592) << endl;
    cout << getMax(1.0f, 3.4f) << endl;
    cout << getMax('a', 'c') << endl;
    cout << getMax(Cents(5), Cents(9)) << endl;
  }

  /* stdout
  2
  3.14
  3.4
  c
  9 cents
  */
C++/Templates 2021. 3. 21. 23:57

템플릿 (Templates)

함수 템플릿 (Function Templates)

클래스 템플릿 (Class Template)

자료형이 아닌 템플릿 매개변수 (Non-type Templates Parameters)

함수 템플릿 특수화 (Function Templates Specialization)

클래스 템플릿 특수화 (Class Templates Specialization)

템플릿 부분 특수화 (Templates Partial Specialization)

포인터 템플릿 특수화(Pointer Templates Specialization)

멤버 함수를 한 번 더 템플릿화하기

C++/TBC++ 2021. 3. 21. 23:57

따라하며 배우는 C++ 13장

템플릿 (Templates)


참고

'C++ > TBC++' 카테고리의 다른 글

C++ 따라하며 배우는 C++ 15장  (0) 2021.03.22
C++ 따라하며 배우는 C++ 14장  (0) 2021.03.22
따라하며 배우는 C++ 12장  (0) 2021.03.20
따라하며 배우는 C++ 11장  (0) 2021.03.19
따라하며 배우는 C++ 10장  (0) 2021.03.19
C++/Class 2021. 3. 21. 23:56

유도 클래스에서 출력 연산자 사용하기

  • 연산자 오버로딩할 때만 사용하는게 아니라, 다형성을 더 유연하게 구현할 때도 사용되는 기법이다.

예제

  • 멤버 함수로 만들 수 없는 연산자를 오버라이딩한 것 처럼 구현하기 위해서 다음과 같은 기법을 사용할 수 있다.

    #include <iostream>
    
    class Base
    {
    public:
      friend std::ostream& operator << (std::ostream& out, const Base& b)
      {
        return b.print(out);
      }
    
      virtual std::ostream& print(std::ostream& out) const
      {
        out << "Base";
        return out;
      }
    };
    
    class Derived : public Base
    {
    public:    
      virtual std::ostream& print(std::ostream& out) const override
      {
        out << "Derived";
        return out;
      }
    };
    
    int            main()
    {
      using namespace std;
    
      Base b;
      cout << b << '\n';
    
      Derived d;
      cout << d << '\n';
    
      Base& bref = d;
      cout << bref << '\n';
    }
    
    /* stdout
    Base
    Derived
    Derived
    */
C++/Class 2021. 3. 21. 23:54

동적 형변환 (Dynamic Casting)

  • dynamic_cast는 캐스팅이 실패하면 nullptr를 반환한다.

예제

  • 간단한 예제

    #include <iostream>
    
    class Base
    {
    public:
      int    i_ = 0;
    
      virtual void print()
      {
        std::cout << "I'm Base\n";
      }
    };
    
    class Derived1 : public Base
    {
    public:
      int    j_ = 1024;
    
      virtual void print() override
      {
        std::cout << "I'm Derived\n";
      }
    };
    
    int            main()
    {
      using namespace std;
    
      Derived1    d1;
      Base*        base = &d1;
    
      d1.j_ *= 2;
    
      auto*        base_to_d1 = dynamic_cast<Derived1*>(base);
      cout << base_to_d1->j_ << endl;
      base_to_d1->j_ /= 8;
    
      cout << d1.j_ << endl;
    }
    
    /* stdout
    2048
    256
    */

  • 캐스팅 실패 예제

    #include <iostream>
    
    class Base
    {
    public:
      int    i_ = 0;
    
      virtual void print()
      {
        std::cout << "I'm Base\n";
      }
    };
    
    class Derived1 : public Base
    {
    public:
      int    j_ = 1024;
    
      virtual void print() override
      {
        std::cout << "I'm Derived\n";
      }
    };
    
    class Derived2 : public Base
    {
    public:
      std::string    name_ = "Dr. Two";
    
      virtual void print() override
      {
        std::cout << "I'm Derived\n";
      }
    };
    
    int            main()
    {
      using namespace std;
    
      Derived1    d1;
      Base*        base = &d1;
    
      d1.j_ *= 2;
    
      auto*        base_to_d1 = dynamic_cast<Derived2*>(base);
      if (base_to_d1 != nullptr)
        base_to_d1->print();
      else
        cout << "Failed to cast\n";
    }
    
    /* stdout
    Failed to cast
    */

정적 형변환 (Static Casting)

  • 최대한 변환되도록 캐스팅한다.

  • 위의 경우 가급적 Dynamic Casting을 사용하는게 안전하다.

    #include <iostream>
    
    class Base
    {
    public:
      int    i_ = 0;
    
      virtual void print()
      {
        std::cout << "I'm Base\n";
      }
    };
    
    class Derived1 : public Base
    {
    public:
      int    j_ = 1024;
    
      virtual void print() override
      {
        std::cout << "I'm Derived\n";
      }
    };
    
    class Derived2 : public Base
    {
    public:
      std::string    name_ = "Dr. Two";
    
      virtual void print() override
      {
        std::cout << "I'm Derived\n";
      }
    };
    
    int            main()
    {
      using namespace std;
    
      Derived1    d1;
      Base*        base = &d1;
    
      d1.j_ *= 2;
    
      auto*        base_to_d1 = static_cast<Derived2*>(base);
      if (base_to_d1 != nullptr)
        base_to_d1->print();
      else
        cout << "Failed to cast\n";
    }
    
    /* stdout
    I'm Derived
    */
C++/Library 2021. 3. 21. 23:53

reference_wrapper

  • <functional> 라이브러리

  • 템플릿 등에 레퍼런스로 전달할 수 있게 해주는 클래스이다.


예제

  #include <iostream>
  #include <vector>
  #include <functional>

  class Base
  {
  public:
    int    i_ = 0;

    virtual void print()
    {
      std::cout << "I'm Base\n";
    }
  };

  class Derived : public Base
  {
  public:
    int    j_ = 1;

    virtual void print() override
    {
      std::cout << "I'm Derived\n";
    }
  };

  void        doSomething(Base& b)
  {
    b.print();
  }

  int            main()
  {
    using namespace std;

    Base    b;
    Derived    d;

    vector<std::reference_wrapper<Base>> my_vec;
    my_vec.push_back(b);
    my_vec.push_back(d);

    for (auto& e : my_vec)
      e.get().print();
  }

  /* stdout
  I'm Base
  I'm Derived
  */

'C++ > Library' 카테고리의 다른 글

C++ 출력 스트림 끊기  (0) 2021.03.24
C++ std::exception  (0) 2021.03.22
C++ IntArray  (0) 2021.03.19
C++ initializer_list  (0) 2021.03.19
C++ chrono  (0) 2021.03.16
C++/Class 2021. 3. 21. 23:51

객체 잘림 (Object Slicing)

  • 부모 클래스 자료형에 자식 클래스 자료형을 대입할 경우, 흔히 말하는 truncation이 발생한다.

예제

  #include <iostream>

  class Base
  {
  public:
    int    i_ = 0;

    virtual void print()
    {
      std::cout << "I'm Base\n";
    }
  };

  class Derived : public Base
  {
  public:
    int    j_ = 1;

    virtual void print() override
    {
      std::cout << "I'm Derived\n";
      std::cout << "j_ : " << j_ << '\n';
    }
  };

  void        doSomething1(Base& b)
  {
    b.print();
  }

  void        doSomething2(Base b)
  {
    b.print();
  }

  int            main()
  {
    using namespace std;

    Derived    d;
    Base&    b1 = d;
    Base    b2 = d;

    b1.print();
    b2.print();

    cout << endl;

    doSomething1(b1);
    doSomething2(b1);
  }

  /* stdout
  I'm Derived
  j_ : 1
  I'm Base

  I'm Derived
  j_ : 1
  I'm Base
  */
  • b1의 경우 레퍼런스로 전달하여 다형성이 적용된다.

  • b2의 경우 값을 복사한 것이므로 자식 클래스(Derived)가 아닌 부모 클래스(Base)로 인식한다.

    • 이 경우 다형성을 사용할 수 없다.

reference_wrapper

  • std::vector의 경우 템플릿에 레퍼런스를 넣을 수 없다.

    • 이럴 때 포인터로 구현을 해도 되지만, 레퍼런스로 구현하려면 reference_wrapper를 사용하면 된다.
C++/Class 2021. 3. 21. 23:50

다이아몬드 상속 (Diamond Polymorphism)

  • 한 자식 클래스가 상속받는 서로 다른 부모 클래스들이, 같은 조부모 클래스를 상속받는 구조

예제

  • 클래스 AB, C가 상속받은 후 DBC를 상속받으면, D::B::AD::C::A가 각각 존재하게 된다.

    #include <iostream>
    
    class PoweredDevice
    {
    public:
      int    i_;
    
      PoweredDevice(const int& power)
      {
        std::cout << "PoweredDevice: " << power << '\n';
      }
    };
    
    class Scanner : public PoweredDevice
    {
    public:
      Scanner(const int& scanner, const int& power)
        : PoweredDevice(power)
      {
        std::cout << "Scanner: " << scanner << '\n';
      }
    };
    
    class Printer : public PoweredDevice
    {
    public:
      Printer(const int& printer, const int& power)
        : PoweredDevice(power)
      {
        std::cout << "Printer: " << printer << '\n';
      }
    };
    
    class Copier : public Scanner, public Printer
    {
    public:
      Copier(const int& scanner, const int& printer, const int& power)
        : Scanner(scanner, power), Printer(printer, power)
      {}
    };
    
    int            main()
    {
      using namespace std;
    
      Copier    cop(1, 2, 3);
      cout << &cop.Scanner::PoweredDevice::i_ << endl;
      cout << &cop.Printer::PoweredDevice::i_ << endl;
    }
    
    /* stdout
    PoweredDevice: 3
    Scanner: 1
    PoweredDevice: 3
    Printer: 2
    0000008F470FF798
    0000008F470FF79C
    */

  • 아래와 같이 상속 설정에서 virtual 키워드를 붙이면 해당 부모 클래스의 인스턴스가 하나만 사용된다.

    • 부모 클래스의 생성자를 따로 호출하지 않는 경우 에러가 발생했다.
    #include <iostream>
    
    class PoweredDevice
    {
    public:
      int    i_;
    
      PoweredDevice(const int& power)
      {
        std::cout << "PoweredDevice: " << power << '\n';
      }
    };
    
    class Scanner : virtual public PoweredDevice  // 여기서 virtual 키워드 사용
    {
    public:
      Scanner(const int& scanner, const int& power)
        : PoweredDevice(power)
      {
        std::cout << "Scanner: " << scanner << '\n';
      }
    };
    
    class Printer : virtual public PoweredDevice  // 여기서 virtual 키워드 사용
    {
    public:
      Printer(const int& printer, const int& power)
        : PoweredDevice(power)
      {
        std::cout << "Printer: " << printer << '\n';
      }
    };
    
    class Copier : public Scanner, public Printer
    {
    public:
      Copier(const int& scanner, const int& printer, const int& power)
        : Scanner(scanner, power), Printer(printer, power)  // 에러
      {}
    };
    
    int            main()
    {
      using namespace std;
    
      Copier    cop(1, 2, 3);
      cout << &cop.Scanner::PoweredDevice::i_ << endl;
      cout << &cop.Printer::PoweredDevice::i_ << endl;
    }
    error C2512: 'PoweredDevice::PoweredDevice': no appropriate default constructor available
    • 부모 클래스(PoweredDevice)의 생성자를 명시하지 않으면 기본 생성자가 호출되는데, 현재 인자가 없는 생성자가 존재하지 않으므로 에러가 발생한 것이다.
  • 에러가 나는 부분에 부모 클래스의 생성자를 호출하거나, 기본 생성자를 만들어주면 에러가 발생하지 않는다.

    #include <iostream>
    
    class PoweredDevice
    {
    public:
      int    i_;
    
      PoweredDevice(const int& power)
      {
        std::cout << "PoweredDevice: " << power << '\n';
      }
    };
    
    class Scanner : virtual public PoweredDevice
    {
    public:
      Scanner(const int& scanner, const int& power)
        : PoweredDevice(power)
      {
        std::cout << "Scanner: " << scanner << '\n';
      }
    };
    
    class Printer : virtual public PoweredDevice
    {
    public:
      Printer(const int& printer, const int& power)
        : PoweredDevice(power)
      {
        std::cout << "Printer: " << printer << '\n';
      }
    };
    
    class Copier : public Scanner, public Printer
    {
    public:
      Copier(const int& scanner, const int& printer, const int& power)
        : Scanner(scanner, power), Printer(printer, power), PoweredDevice(power)
      {}
    };
    
    int            main()
    {
      using namespace std;
    
      Copier    cop(1, 2, 3);
      cout << &cop.Scanner::PoweredDevice::i_ << endl;
      cout << &cop.Printer::PoweredDevice::i_ << endl;
    }
    
    /* stdout
    PoweredDevice: 3
    Scanner: 1
    Printer: 2
    00000094DE1BFA58
    00000094DE1BFA58
    */
    • 아까와 달리 PoweredDevice: 3이 한 번만 출력된 것을 볼 수 있고, 같은 메모리를 사용하는 것을 알 수 있다.
C++/Class 2021. 3. 21. 23:47

인터페이스 클래스 (Interface Class)

  • 순수 가상 함수를 이용하여 만드는 클래스

  • 특별한 기능을 하지는 않고 자식 클래스들을 편하게 다루기 위해 사용한다.


예제

  #include <iostream>

  class IErrorLog
  {
  public:
    virtual ~IErrorLog() {}

    virtual bool reportError(const char* errorMessage) = 0;
  };

  class FileErrorLog : public IErrorLog
  {
  public:
    bool reportError(const char* errorMessage) override
    {
      std::cout << "Writing error to a file\n";
      return true;
    }
  };

  class ConsoleErrorLog : public IErrorLog
  {
  public:
    bool reportError(const char* errorMessage) override
    {
      std::cout << "Printing error to a console\n";
      return true;
    }
  };

  void        doSomething(IErrorLog& log)
  {
    log.reportError("Runtime error!!");
  }

  int            main()
  {
    FileErrorLog    file_log;
    ConsoleErrorLog    console_log;

    doSomething(file_log);
    doSomething(console_log);
  }

  /* stdout
  Writing error to a file
  Printing error to a console
  */
C++/Class 2021. 3. 21. 23:46

추상 기본 클래스 (Abstract class)

  • 순수 가상 함수가 하나라도 존재하는 클래스

  • 순수 가상 함수가 존재하는데 오버라이딩하는 함수가 없을 경우, 에러가 발생한다.


예제

  #include <iostream>

  class Animal
  {
  protected:
    std::string    name_;

  public:
    Animal(const std::string &name_in)
      : name_(name_in)
    {}

    std::string        getName() { return name_; }

    virtual void    speak() const = 0;  // pure virtual function
  };

  class Cat : public Animal
  {
  public:
    Cat(const std::string& name_in)
      : Animal(name_in)
    {}

    void            speak() const
    {
      std::cout << name_ << " Meow\n";
    }
  };

  class Dog : public Animal
  {
  public:
    Dog(const std::string& name_in)
      : Animal(name_in)
    {}

    void            speak() const
    {
      std::cout << name_ << " Woof\n";
    }
  };

  class Cow : public Animal
  {
  public:
    Cow(const std::string& name_in)
      : Animal(name_in)
    {}
  };

  int            main()
  {
    using namespace std;

    Animal    ani("hello");  // 에러

    Cow        cow("cau");      // 에러
  }
  error C2259: 'Animal': cannot instantiate abstract class
  error C2259: 'Cow': cannot instantiate abstract class
  • 첫 번째 에러는 추상 클래스를 인스턴스화하려해서 발생하는 에러이다.

  • 두 번째 에러도 마찬가지인데, Cow 클래스에서 speak 함수를 오버라이딩하지 않으므로 Cow 클래스 역시 추상 클래스이다.

C++/Class 2021. 3. 21. 23:45

순수 가상 함수 (Pure Virtual Function)

  • body가 없고 값이 0으로 설정된 virtual 함수를 의미한다.

  • body를 외부에서 정의할 수는 있는데 의미는 없다.


예제

  #include <iostream>

  class Animal
  {
  protected:
    std::string    name_;

  public:
    Animal(const std::string &name_in)
      : name_(name_in)
    {}

    std::string        getName() { return name_; }

    virtual void    speak() const = 0;  // pure virtual function
  };

  class Cat : public Animal
  {
  public:
    Cat(const std::string & name_in)
      : Animal(name_in)
    {}

    void            speak() const
    {
      std::cout << name_ << " Meow\n";
    }
  };

  class Dog : public Animal
  {
  public:
    Dog(const std::string& name_in)
      : Animal(name_in)
    {}

    void            speak() const
    {
      std::cout << name_ << " Woof\n";
    }
  };

  int            main()
  {
    using namespace std;

    Cat("nyang").speak();
    Dog("meong").speak();
  }

  /* stdout
  nyang Meow
  meong Woof
  */
C++/Class 2021. 3. 20. 17:49

가상 (함수) 테이블 (Virtual Tables)

  • virtual 키워드를 사용하면 내부적으로 가상 테이블의 주소가 저장된다.

예제

간단한 상속 예제

  class Base
  {
  public:
    // FunctionPointer *_vptr;
    virtual void func1() {}
    virtual void func2() {}
  };

  class Derived : public Base
  {
  public:
    // FunctionPointer *_vptr;
    virtual void func1() {}
  };
  • 여기서 클래스들의 가상 테이블은 다음과 같다.

    Base VTable 가리키고 있는 함수
    func1 Base::func1
    func2 Base::func2

    Derived VTable 가리키고 있는 함수
    func1 Derived::func1
    func2 Base::func2

사이즈 출력해보기

  • 가상 함수를 사용하지 않는 경우

    • 객체의 크기가 1바이트씩 나온다.
    #include <iostream>
    
    class Base
    {
    public:
      // FunctionPointer *_vptr;
      void func1() {}
      void func2() {}
    };
    
    class Derived : public Base
    {
    public:
      // FunctionPointer *_vptr;
      void func1() {}
    };
    
    int            main()
    {
      using namespace std;
    
      cout << sizeof(Base) << endl;
      cout << sizeof(Derived) << endl;
    }
    
    /* stdout
    1
    1
    */
  • 가상 함수를 사용할 경우

    • x86에서는 4바이트, x64에서는 8바이트가 나온다.

      • 이는 주소의 크기이다.
    #include <iostream>
    
    class Base
    {
    public:
      // FunctionPointer *_vptr;
      virtual void func1() {}
      virtual void func2() {}
    };
    
    class Derived : public Base
    {
    public:
      // FunctionPointer *_vptr;
      virtual void func1() {}
    };
    
    int            main()
    {
      using namespace std;
    
      cout << sizeof(Base) << endl;
      cout << sizeof(Derived) << endl;
    }
    
    /* stdout (x86)
    4
    4
    */
    
    /* stdout (x64)
    8
    8
    */
C++/Class 2021. 3. 20. 17:47

동적 바인딩 (Dynamic Binding)

  • Late Binding

  • 정적 바인딩보다 느리다.

    • 대신 프로그래밍이 좀 더 유연해진다.
  • 런타임에 주소를 쫓아가서 함수를 실행시키는 경우이다.


예제

  #include <iostream>

  int            add(int x, int y)
  {
    return (x + y);
  }

  int            subtract(int x, int y)
  {
    return (x - y);
  }

  int            multiply(int x, int y)
  {
    return (x * y);
  }

  int            main()
  {
    using namespace std;

    int    x, y;
    cin >> x >> y;

    int    op;
    cout << "0 : add, 1 : subtract, 2 : multiply\n";
    cin >> op;

    int(*func_ptr)(int, int) = nullptr;
    switch (op)
    {
    case 0: func_ptr = add; break;
    case 1: func_ptr = subtract; break;
    case 2: func_ptr = multiply; break;
    }

    cout << func_ptr(x, y) << endl;
  }
C++/Class 2021. 3. 20. 17:46

정적 바인딩 (Static Binding)

  • Early Binding

  • 동적 바인딩보다 빠르다.

  • 빌드 타임에 모든 변수명이나 함수명이 정의된 경우이다.


예제

  #include <iostream>

  int            add(int x, int y)
  {
    return (x + y);
  }

  int            subtract(int x, int y)
  {
    return (x - y);
  }

  int            multiply(int x, int y)
  {
    return (x * y);
  }

  int            main()
  {
    using namespace std;

    int    x, y;
    cin >> x >> y;

    int    op;
    cout << "0 : add, 1 : subtract, 2 : multiply\n";
    cin >> op;

    int    result = 0;
    switch (op)
    {
    case 0: result = add(x, y); break;
    case 1: result = subtract(x, y); break;
    case 2: result = multiply(x, y); break;
    }

    cout << result << endl;
  }

'C++ > Class' 카테고리의 다른 글

C++ 가상 (함수) 테이블 (Virtual Tables)  (0) 2021.03.20
C++ 동적 바인딩 (Dynamic Binding)  (0) 2021.03.20
C++ 가상 소멸자  (0) 2021.03.20
C++ 공변 반환형(Covariant Return Type)  (0) 2021.03.20
C++ Override, Final  (0) 2021.03.20
C++/Class 2021. 3. 20. 17:45

가상 소멸자

  • virtual 키워드를 소멸자에도 붙일 수 있다.

예제

  • 가상 소멸자를 사용하지 않은 기본 예제

    #include <iostream>
    
    class Base
    {
    public:
      ~Base() { std::cout << "~Base()" << std::endl; }
    };
    
    class Derived : public Base
    {
      int *array_;
    
    public:
      Derived(const int& length)
      {
        array_ = new int[length];
      }
    
      ~Derived()
      {
        std::cout << "~Derived()" << std::endl;
        delete[] array_;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Derived    derived(5);
    }
    
    /* stdout
    ~Derived()
    ~Base()
    */

  • 메모리 누수 예제

    #include <iostream>
    
    class Base
    {
    public:
      ~Base() { std::cout << "~Base()" << std::endl; }
    };
    
    class Derived : public Base
    {
      int *array_;
    
    public:
      Derived(const int& length)
      {
        array_ = new int[length];
      }
    
      ~Derived()
      {
        std::cout << "~Derived()" << std::endl;
        delete[] array_;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Derived    *derived = new Derived(5);
      Base* base = derived;
    
      delete base;
    }
    
    /* stdout
    ~Base()
    */
  • 부모와 자식 클래스의 소멸자에 virtual 키워드를 붙이고, 자식 클래스의 소멸자에 override까지 붙여주면 좋다.

    #include <iostream>
    
    class Base
    {
    public:
      virtual ~Base() { std::cout << "~Base()" << std::endl; }
    };
    
    class Derived : public Base
    {
      int *array_;
    
    public:
      Derived(const int& length)
      {
        array_ = new int[length];
      }
    
      virtual ~Derived() override
      {
        std::cout << "~Derived()" << std::endl;
        delete[] array_;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Derived    *derived = new Derived(5);
      Base* base = derived;
    
      delete base;
    }
    
    /* stdout
    ~Derived()
    ~Base()
    */

'C++ > Class' 카테고리의 다른 글

C++ 동적 바인딩 (Dynamic Binding)  (0) 2021.03.20
C++ 정적 바인딩 (Static Binding)  (0) 2021.03.20
C++ 공변 반환형(Covariant Return Type)  (0) 2021.03.20
C++ Override, Final  (0) 2021.03.20
C++ 가상 함수와 다형성 (virtual)  (0) 2021.03.20
C++/Class 2021. 3. 20. 17:43

공변 반환형(Covariant Return Type)

  • 포인터나 레퍼런스의 전달에서, 상속 관계의 클래스 반환 자료형에 관한 내용이다.

예제

  • 다음은 가상 멤버 함수 getThis로 받은 포인터를 통해 일반 멤버 함수 print를 호출하는 예제이다.

    #include <iostream>
    
    class A
    {
    public:
      void print() { std::cout << "A" << std::endl; }
    
      virtual A* getThis() 
      { 
        std::cout << "A::getThis()" << std::endl;
        return this; 
      }
    };
    
    class B : public A
    {
    public:
      void print() { std::cout << "B" << std::endl; }
    
      virtual B* getThis() 
      {
        std::cout << "B::getThis()" << std::endl;
        return this; 
      }
    };
    
    int        main()
    {
      using namespace std;
    
      A        a;
      B        b;
    
      A&    ref = b;
      b.getThis()->print();
      ref.getThis()->print();
    
      cout << typeid(b.getThis()).name() << endl;
      cout << typeid(ref.getThis()).name() << endl;
    }
    
    /* stdout
    B::getThis()
    B
    B::getThis()
    A
    class B *
    class A *
    */
    • stdout에서 볼 수 있듯이, ref.getThis()B::getThis()를 호출하고 B* 형태의 주소를 반환한다.

      • 하지만 ref의 자료형이 A 클래스의 레퍼런스이므로, A* 형태의 주소로 인식되어 A::print()가 실행되는 것이다.

      • typeid(ref.getThis()).name() 함수의 결과가 class A * 형태인 것으로도 유추할 수 있다.

'C++ > Class' 카테고리의 다른 글

C++ 정적 바인딩 (Static Binding)  (0) 2021.03.20
C++ 가상 소멸자  (0) 2021.03.20
C++ Override, Final  (0) 2021.03.20
C++ 가상 함수와 다형성 (virtual)  (0) 2021.03.20
C++ 다형성의 기본 개념  (0) 2021.03.20
C++/Class 2021. 3. 20. 17:42

Override, Final

Override

  • 부모 클래스에서 virtual로 선언한 함수를 자식 클래스에서 오버라이딩한다는 의미의 키워드이다.

    • 컴파일러는 프로그래머의 의도를 정확하게 알 수 없기 때문에, 다음과 같이 프로그래머가 실수할 수 있는 상황이 생긴다.

예제

  • print 함수를 오버라이딩하려는데, 실수로 파라미터를 int가 아닌 short로 작성한 상황이다.

    #include <iostream>
    
    class A
    {
    public:
      virtual void print(int x)
      {
        std::cout << "A" << std::endl;
      }
    };
    
    class B : public A
    {
    public:
      void print(short x)
      {
        std::cout << "B" << std::endl;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      A    a;
      B    b;
    
      A&    ref = b;
      ref.print(1);
    }
    
    /* stdout
    A
    */
  • 이럴 때 함수의 body 앞에 override 키워드를 사용하면 컴파일러에서 감지할 수 있다.

    #include <iostream>
    
    class A
    {
    public:
      virtual void print(int x)
      {
        std::cout << "A" << std::endl;
      }
    };
    
    class B : public A
    {
    public:
      void print(short x) override  // 에러
      {
        std::cout << "B" << std::endl;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      A    a;
      B    b;
    
      A&    ref = b;
      ref.print(1);
    }
    error C3668: 'B::print': method with override specifier 'override' did not override any base class methods

Final

  • override처럼 함수의 body 전에 final 키워드를 붙이면, 자식 클래스에서 오버라이딩하지 않는다는 뜻으로 간주한다.

    • 만약 final 키워드를 작성했는데 자식 클래스에서 오버라이딩할 경우, 에러가 발생한다.
    #include <iostream>
    
    class A
    {
    public:
      virtual void print()
      {
        std::cout << "A" << std::endl;
      }
    };
    
    class B : public A
    {
    public:
      void print() final
      {
        std::cout << "B" << std::endl;
      }
    };
    
    class C : public B
    {
    public:
      virtual void print()  // 에러
      {
        std::cout << "C" << std::endl;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      A    a;
      B    b;
    
      A&    ref = b;
      ref.print();
    }
    error C3248: 'B::print': function declared as 'final' cannot be overridden by 'C::print'

'C++ > Class' 카테고리의 다른 글

C++ 가상 소멸자  (0) 2021.03.20
C++ 공변 반환형(Covariant Return Type)  (0) 2021.03.20
C++ 가상 함수와 다형성 (virtual)  (0) 2021.03.20
C++ 다형성의 기본 개념  (0) 2021.03.20
C++ 다형성 (Polymorphism)  (0) 2021.03.20
C++/Class 2021. 3. 20. 17:34

가상 함수와 다형성 (virtual)

  • 상속 최상위 클래스에서 멤버 함수를 정의할 때 virtual 키워드를 사용하면, 가장 말단까지 오버라이딩된 함수를 찾아서 실행한다.

    #include <iostream>
    
    class A
    {
    public:
      virtual void print()
      {
        std::cout << "A" << std::endl;
      }
    };
    
    class B : public A
    {
    public:
      void print()
      {
        std::cout << "B" << std::endl;
      }
    };
    
    class C : public B
    {
    public:
      void print()
      {
        std::cout << "C" << std::endl;
      }
    };
    
    class D : public C
    {
    public:
      void print()
      {
        std::cout << "D" << std::endl;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      A    a;
      B    b;
      C    c;
      D    d;
    
      A& ref1 = c;
      ref1.print();
    
      B& ref2 = c;
      ref2.print();
    }
    
    /* stdout
    C
    C
    */

  • 관습적으로 오버라이딩한 함수의 앞에는 virtual 키워드를 붙이는 편이라고 한다.

    • 디버깅할 때의 편의성을 위한 것이다.

  • virtual은 스택 구조로 함수를 호출하는게 아니라 virtual table에서 찾아서 호출하는 것이다.

    • 호출이 빈번한 함수에서 사용하면 성능이 저하될 수도 있다.

'C++ > Class' 카테고리의 다른 글

C++ 공변 반환형(Covariant Return Type)  (0) 2021.03.20
C++ Override, Final  (0) 2021.03.20
C++ 다형성의 기본 개념  (0) 2021.03.20
C++ 다형성 (Polymorphism)  (0) 2021.03.20
C++ 다중 상속  (0) 2021.03.20
C++/Class 2021. 3. 20. 17:32

다형성의 기본 개념

  • 부모 클래스의 포인터로 자식 클래스의 주소를 가리키면, 부모 클래스의 인스턴스로 인식한다.

예제

  • 자식 클래스에서 멤버 함수를 오버라이딩해도, 부모 클래스의 포인터에서 접근하면 부모 클래스의 멤버 함수가 호출된다.

    #include <iostream>
    
    class Animal
    {
    protected:
      std::string    name_;
    
    public:
      Animal(const std::string & name_in)
        : name_(name_in)
      {}
    
      std::string    getName() { return name_; }
    
      void speak() const
      {
        std::cout << name_ << " ??? " << std::endl;
      }
    };
    
    class Cat : public Animal
    {
    public:
      Cat(const std::string & name_in)
        : Animal(name_in)
      {}
    
      void speak() const
      {
        std::cout << name_ << " Meow " << std::endl;
      }
    };
    
    class Dog : public Animal
    {
    public:
      Dog(const std::string & name_in)
        : Animal(name_in)
      {}
    
      void speak() const
      {
        std::cout << name_ << " Woof " << std::endl;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Animal    animal("my animal");
      Cat        cat("my cat");
      Dog        dog("my dog");
    
      animal.speak();
      cat.speak();
      dog.speak();
    
      Animal* ptr_animal1 = &cat;
      Animal* ptr_animal2 = &dog;
    
      ptr_animal1->speak();
      ptr_animal2->speak();
    }
    
    /* stdout
    my animal ???
    my cat Meow
    my dog Woof
    my cat ???
    my dog ???
    */

  • 부모 클래스의 멤버 함수를 정의할 때 virtual 키워드를 붙이면, 자식 클래스에서 오버라이딩한 멤버 함수를 사용하도록 한다.

    #include <iostream>
    
    class Animal
    {
    protected:
      std::string    name_;
    
    public:
      Animal(const std::string & name_in)
        : name_(name_in)
      {}
    
      std::string    getName() { return name_; }
    
      virtual void speak() const
      {
        std::cout << name_ << " ??? " << std::endl;
      }
    };
    
    class Cat : public Animal
    {
    public:
      Cat(const std::string & name_in)
        : Animal(name_in)
      {}
    
      void speak() const
      {
        std::cout << name_ << " Meow " << std::endl;
      }
    };
    
    class Dog : public Animal
    {
    public:
      Dog(const std::string & name_in)
        : Animal(name_in)
      {}
    
      void speak() const
      {
        std::cout << name_ << " Woof " << std::endl;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Cat    cats[] = { Cat("cat1"), Cat("cat2"), Cat("cat3"), Cat("cat4"), Cat("cat5") };
      Dog    dogs[] = { Dog("dog1"), Dog("dog2") };
    
      for (int i = 0; i < 5; ++i)
        cats[i].speak();
      for (int i = 0; i < 2; ++i)
        dogs[i].speak();
    
      cout << endl;
      Animal* my_animals[] = {&cats[0], &cats[1], &cats[2], &cats[3], &cats[4], &dogs[0], &dogs[1]};
    
      for (int i = 0; i < 7; ++i)
        my_animals[i]->speak();
    }
    
    /* stdout
    cat1 Meow
    cat2 Meow
    cat3 Meow
    cat4 Meow
    cat5 Meow
    dog1 Woof
    dog2 Woof
    
    cat1 Meow
    cat2 Meow
    cat3 Meow
    cat4 Meow
    cat5 Meow
    dog1 Woof
    dog2 Woof
    */

'C++ > Class' 카테고리의 다른 글

C++ Override, Final  (0) 2021.03.20
C++ 가상 함수와 다형성 (virtual)  (0) 2021.03.20
C++ 다형성 (Polymorphism)  (0) 2021.03.20
C++ 다중 상속  (0) 2021.03.20
C++ 상속받은 멤버의 접근 권한 변경  (0) 2021.03.20
C++/Class 2021. 3. 20. 17:30

다형성 (Polymorphism)

다형성의 기본 개념

가상 함수와 다형성

override, final

공변 반환형(Covariant Return Type)

가상 소멸자 (Virtual Destructor)

정적 바인딩 (Static Binding)

동적 바인딩 (Dynamic Binding)

가상 테이블 (Virtual Tables)

순수 가상 함수 (Pure Virtual Function)

추상 기본 클래스 (Abstract class)

인터페이스 클래스 (Interface Class)

다이아몬드 상속 (Diamond Polymorphism)

객체 잘림 (Object Slicing)

동적 형변환 (Dynamic Casting)

유도 클래스에서 출력 연산자 사용하기

'C++ > Class' 카테고리의 다른 글

C++ 가상 함수와 다형성 (virtual)  (0) 2021.03.20
C++ 다형성의 기본 개념  (0) 2021.03.20
C++ 다중 상속  (0) 2021.03.20
C++ 상속받은 멤버의 접근 권한 변경  (0) 2021.03.20
C++ 함수 오버라이딩  (0) 2021.03.20
C++/TBC++ 2021. 3. 20. 17:29

따라하며 배우는 C++ 12장

다형성 (Polymorphism)


참고

'C++ > TBC++' 카테고리의 다른 글

C++ 따라하며 배우는 C++ 14장  (0) 2021.03.22
따라하며 배우는 C++ 13장  (0) 2021.03.21
따라하며 배우는 C++ 11장  (0) 2021.03.19
따라하며 배우는 C++ 10장  (0) 2021.03.19
C++ 따라하며 배우는 C++ 9장  (0) 2021.03.16
C++/Class 2021. 3. 20. 17:28

다중 상속

  • 여러 클래스에서 상속을 받을 수 있다.

    #include <iostream>
    
    class USBDevice
    {
      int    id_;
    
    public:
      USBDevice(int id_in) : id_(id_in) {}
    
      int    getID() { return id_; }
    
      void plugAndPlay() {}
    };
    
    class NetworkDevice
    {
      int    id_;
    
    public:
      NetworkDevice(int id_in) : id_(id_in) {}
    
      int    getID() { return id_; }
    
      void networking() {}
    };
    
    class USBNetworkDevice : public USBDevice, public NetworkDevice
    {
    public:
      USBNetworkDevice(int usb_id, int net_id)
        : USBDevice(usb_id), NetworkDevice(net_id)
      {}
    
      USBNetworkDevice(int common_id)
        : USBDevice(common_id), NetworkDevice(common_id)
      {}
    };
    
    int        main()
    {
      using namespace std;
    
      USBNetworkDevice    my_device(42, 1024);
    
      my_device.networking();
      my_device.plugAndPlay();
    
      cout << my_device.USBDevice::getID() << endl;
      cout << my_device.NetworkDevice::getID() << endl;
    }
    
    /* stdout
    42
    1024
    */

  • 다이아몬드 상속

    • 같은 부모 클래스를 상속받은 두 개의 클래스들을 또 다시 다중 상속받은 손자 클래스의 형태를 다이아몬드 상속이라고 한다.

    • 보기 드문 패턴인 듯하다.

    • 다중 상속을 잘못 사용하면 문제가 발생할 수 있다고 한다.

'C++ > Class' 카테고리의 다른 글

C++ 다형성의 기본 개념  (0) 2021.03.20
C++ 다형성 (Polymorphism)  (0) 2021.03.20
C++ 상속받은 멤버의 접근 권한 변경  (0) 2021.03.20
C++ 함수 오버라이딩  (0) 2021.03.20
C++ 상속과 접근 지정자  (0) 2021.03.20
C++/Class 2021. 3. 20. 17:27

상속받은 멤버의 접근 권한 변경

  • using 키워드 사용

    #include <iostream>
    
    class Base
    {
    protected:
      int    i_;
    };
    
    class Derived : public Base
    {
    public:
      using Base::i_;
    };
    
    int        main()
    {
      using std::cout;
      using std::endl;
    
      Derived    d;
    
      d.i_ = 1024;
      cout << d.i_ << endl;
    }
    
    /* stdout
    1024
    */

  • 부모 클래스에서 public인 멤버 함수를 자식 클래스에서 private로 변경할 수도 있다.

    • 함수의 경우 괄호를 쓰면 안된다.
    #include <iostream>
    
    class Base
    {
    public:
      void print()
      {
        std::cout << "I'm Base\n";
      }
    };
    
    class Derived : public Base
    {
      using Base::print;
    };
    
    int        main()
    {
      using std::cout;
      using std::endl;
    
      Derived    d;
    
      d.print();  // 에러
    }
    error C2248: 'Derived::print': cannot access private member declared in class 'Derived'

  • 혹은 delete를 사용해서 함수를 사용하지 못하게 하는게 편할 수도 있다.

    #include <iostream>
    
    class Base
    {
    public:
      void print()
      {
        std::cout << "I'm Base\n";
      }
    };
    
    class Derived : public Base
    {
      void print() = delete;
    };
    
    int        main()
    {
      using std::cout;
      using std::endl;
    
      Derived    d;
    
      d.print();  // 에러
    }
    error C2280: 'void Derived::print(void)': attempting to reference a deleted function

'C++ > Class' 카테고리의 다른 글

C++ 다형성 (Polymorphism)  (0) 2021.03.20
C++ 다중 상속  (0) 2021.03.20
C++ 함수 오버라이딩  (0) 2021.03.20
C++ 상속과 접근 지정자  (0) 2021.03.20
C++ 상속과 패딩  (0) 2021.03.20

'C++'에 해당되는 글 185건