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++/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++/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++/Class 2021. 3. 20. 17:26

함수 오버라이딩

  • 자식 클래스에서 부모 클래스의 함수명과 같은 함수를 정의할 수 있다.

    • 함수 오버로딩과 비슷하다. 호출하는 클래스에 따라 결정된다는 점이 다르다.

예제

함수 오버라이딩

  #include <iostream>

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

  class Derived : private Base
  {
  public:
    void    print()
    {
      Base::print();
      std::cout << "I'm Derived\n";
    }
  };

  int        main()
  {
    using namespace std;

    Derived().print();
  }

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

연산자 오버라이딩

  • 연산자 오버로딩도 오버라이딩할 수 있다.

  • static_cast를 통해서 부모 클래스로 형변환하면 부모 클래스에서 오버로딩한 연산자도 사용할 수 있다.

    #include <iostream>
    
    class Base
    {
    public:
      friend std::ostream& operator << (std::ostream& out, const Base& b)
      {
        out << "This is Base output\n";
        return (out);
      }
    };
    
    class Derived : private Base
    {
    public:
      friend std::ostream& operator << (std::ostream& out, const Derived& d)
      {
        out << static_cast<Base>(d);
        out << "This is Derived output\n";
        return (out);
      }
    };
    
    int        main()
    {
      using namespace std;
    
      cout << Derived();
    }
    
    /* stdout
    This is Base output
    This is Derived output
    */

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

C++ 다중 상속  (0) 2021.03.20
C++ 상속받은 멤버의 접근 권한 변경  (0) 2021.03.20
C++ 상속과 접근 지정자  (0) 2021.03.20
C++ 상속과 패딩  (0) 2021.03.20
C++ 유도된 클래스들의 소멸 순서  (0) 2021.03.19
C++/Class 2021. 3. 20. 17:24

상속과 접근 지정자

  • 상속 적용 시 부모 클래스 앞에 public, protected, private와 같은 접근 지정자를 붙인다.

    • 자식 클래스에서 기본적으로 부모 클래스의 멤버들을 어떤 권한으로 받아들일지에 관한 것이다.

예제

  • 아래 예제의 경우 public으로 상속받았으므로 멤버들의 기본 접근 권한이 public이 된다.

    • protected, privatepublic보다 보안이 더 강하므로 그대로 적용된다.
    #include <iostream>
    
    class Base
    {
    public:
      int    public_;
    protected:
      int    protected_;
    private:
      int private_;
    };
    
    class Derived : public Base
    {
    };
    
    int        main()
    {
      using namespace std;
    
      Derived d;
    
      d.public_ = 1024;
    }
  • public 대신 protected를 사용하는 예제

    • public 멤버가 Derived 클래스에서는 protected로 적용된다.

      • Derived 클래스 내부에서는 public_, protected_ 모두 접근할 수 있다.
    #include <iostream>
    
    class Base
    {
    public:
      int    public_;
    protected:
      int    protected_;
    private:
      int    private_;
    };
    
    class Derived : protected Base
    {
    };
    
    int        main()
    {
      using namespace std;
    
      Derived d;
    
      d.public_ = 1024;  // 에러
    }
    • 다음과 같이 접근 오류가 발생한다.

      error C2247: 'Base::public_' not accessible because 'Derived' uses 'protected' to inherit from 'Base'
    • Base 클래스의 멤버가 Derived 클래스 안으로 다음과 같이 들어온 것처럼 생각하면 된다.

      class Derived : protected Base
      {
      protected:
        int    Base::public_;
        int    Base::protected_;
      };

2번 상속하는 경우

  • Base -> Derived -> GrandChild로 상속이 적용되는 예제

    • Derived 클래스가 Base 클래스를 protected로 받은 경우

      • GrandChild 클래스에서 Base 클래스의 protected 멤버까지 접근할 수 있다.
      #include <iostream>
      
      class Base
      {
      public:
        int    public_;
      protected:
        int    protected_;
      private:
        int private_;
      };
      
      class Derived : protected Base
      {
      public:
        Derived()
        {
          Base::public_;
          Base::protected_;
        }
      };
      
      class GrandChild : public Derived
      {
      public:
        GrandChild()
        {
          Derived::Base::public_;
          Derived::Base::protected_;
        }
      };
      
      int        main()
      {
        using namespace std;
      
        Derived d;
      }
    • Derived 클래스가 Base 클래스를 private로 받은 경우

      #include <iostream>
      
      class Base
      {
      public:
        int    public_;
      protected:
        int    protected_;
      private:
        int private_;
      };
      
      class Derived : private Base
      {
      public:
        Derived()
        {
          Base::public_;
          Base::protected_;
        }
      };
      
      class GrandChild : public Derived
      {
      public:
        GrandChild()
        {
          Derived::Base::public_;    // 에러
          Derived::Base::protected_; // 에러
        }
      };
      
      int        main()
      {
        using namespace std;
      
        Derived d;
      }
      • Derived 클래스에서는 Base 클래스의 멤버가 private 상태이기 때문에 접근할 수 있다.

      • 하지만 GrandChild 클래스에서는 Derived 클래스의 private 멤버에 접근할 수 없으므로 다음과 같은 에러가 발생한다.

        error C2247: 'Base::public_' not accessible because 'Derived' uses 'private' to inherit from 'Base'

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

C++ 상속받은 멤버의 접근 권한 변경  (0) 2021.03.20
C++ 함수 오버라이딩  (0) 2021.03.20
C++ 상속과 패딩  (0) 2021.03.20
C++ 유도된 클래스들의 소멸 순서  (0) 2021.03.19
C++ 유도된 클래스들의 생성 순서  (0) 2021.03.19
C++/Class 2021. 3. 20. 17:19

상속과 패딩

  • 일반적인 구조체와 마찬가지로 패딩이 끼게 된다.

    • 패딩이 없는 예제 (int, float)

      #include <iostream>
      
      class Mother
      {
        int i_;
      
      public:
        Mother(const int & i_in = 0)
          : i_(i_in)
        {
          std::cout << "Mother construction\n";
        }
      };
      
      class Child : public Mother
      {
        float d_;
      
      public:
        Child()
          : d_(1.0), Mother(1024)
        {
          std::cout << "Child construction\n";
        }
      };
      
      int        main()
      {
        using namespace std;
      
        cout << sizeof(Mother) << endl;
        cout << sizeof(Child) << endl;
      }
      
      /* stdout
      4
      8
      */
    • 4바이트 패딩 예제 (int, double)

      #include <iostream>
      
      class Mother
      {
        int i_;
      
      public:
        Mother(const int & i_in = 0)
          : i_(i_in)
        {
          std::cout << "Mother construction\n";
        }
      };
      
      class Child : public Mother
      {
        double d_;
      
      public:
        Child()
          : d_(1.0), Mother(1024)
        {
          std::cout << "Child construction\n";
        }
      };
      
      int        main()
      {
        using namespace std;
      
        cout << sizeof(Mother) << endl;
        cout << sizeof(Child) << endl;
      }
      
      /* stdout
      4
      16
      */
    • 끝에 패딩이 4바이트 더 붙은 예제 (int, double, int)

      #include <iostream>
      
      class Mother
      {
        int i_;
      
      public:
        Mother(const int & i_in = 0)
          : i_(i_in)
        {
          std::cout << "Mother construction\n";
        }
      };
      
      class Child : public Mother
      {
        double d_;
        int    tmp_;
      
      public:
        Child()
          : d_(1.0), Mother(1024)
        {
          std::cout << "Child construction\n";
        }
      };
      
      int        main()
      {
        using namespace std;
      
        cout << sizeof(Mother) << endl;
        cout << sizeof(Child) << endl;
      }
      
      /* stdout
      4
      24
      */

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

C++ 함수 오버라이딩  (0) 2021.03.20
C++ 상속과 접근 지정자  (0) 2021.03.20
C++ 유도된 클래스들의 소멸 순서  (0) 2021.03.19
C++ 유도된 클래스들의 생성 순서  (0) 2021.03.19
C++ 상속 Teacher-Student 예제  (0) 2021.03.19
C++/Class 2021. 3. 19. 19:49

유도된 클래스들의 소멸 순서

  • 말단(자식)부터 소멸된다고 생각하면 된다.

    #include <iostream>
    
    class A
    {
    public:
      A(int a = 1024)
      {
        std::cout << "A : " << a << std::endl;
      }
    
      ~A()
      {
        std::cout << "Destructor A" << std::endl;
      }
    };
    
    class B : public A
    {
    public:
      B(double b = 3.14)
      {
        std::cout << "B : " << b << std::endl;
      }
    
      ~B()
      {
        std::cout << "Destructor B" << std::endl;
      }
    };
    
    class C : public B
    {
    public:
      C(char c)
      {
        std::cout << "C : " << c << std::endl;
      }
    
      ~C()
      {
        std::cout << "Destructor C" << std::endl;
      }
    };
    
    int        main()
    {
      using namespace std;
    
      C c('a');
    }
    
    /* stdout
    A : 1024
    B : 3.14
    C : a
    Destructor C
    Destructor B
    Destructor A
    */

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

C++ 상속과 접근 지정자  (0) 2021.03.20
C++ 상속과 패딩  (0) 2021.03.20
C++ 유도된 클래스들의 생성 순서  (0) 2021.03.19
C++ 상속 Teacher-Student 예제  (0) 2021.03.19
C++ 상속 기본 예제  (0) 2021.03.19
C++/Class 2021. 3. 19. 19:48

유도된 클래스들의 생성 순서

  • 당연하게도 부모 클래스부터 생성된다.

    #include <iostream>
    
    class Mother
    {
      int i_;
    
    public:
      Mother()
        : i_(1)
      {
        std::cout << "Mother construction\n";
      }
    };
    
    class Child : public Mother
    {
      double d_;
    
    public:
      Child()
        : d_(1.0)
      {
        std::cout << "Child construction\n";
      }
    };
    
    int        main()
    {
      using namespace std;
    
      Child c;
    }
    
    /* stdout
    Mother construction
    Child construction
    */

  • 멤버 이니셜라이저 리스트

    • 위의 예제에서 Mother::i_의 접근 권한을 public으로 변경하고, Child 생성자에서 멤버 이니셜라이저 리스트로 값을 설정하려하면 다음과 같은 에러가 발생한다.

      error C2614: 'Child': illegal member initialization: 'i_' is not a base or member
    • i_ 대신에, 존재하지 않는 변수인 dummy로 변경하여 컴파일해봐도 같은 오류가 발생한다.

      error C2614: 'Child': illegal member initialization: 'dummy' is not a base or member
    • 즉, 멤버 이니셜라이저 리스트로 초기화할 수 있는건 해당 클래스의 멤버 변수만 해당되는 것을 알 수 있다.

    • 대신 멤버 이니셜라이저 리스트에서 부모 클래스의 생성자를 호출할 수 있고, C++11부터 위임 생성자도 사용 가능하므로 이를 사용하면 될 것 같다.

      #include <iostream>
      
      class Mother
      {
        int i_;
      
      public:
        Mother(const int & i_in = 0)
          : i_(i_in)
        {
          std::cout << "Mother construction\n";
        }
      };
      
      class Child : public Mother
      {
        double d_;
      
      public:
        Child()
          : d_(1.0), Mother(1024)
        {
          std::cout << "Child construction\n";
        }
      };
      
      int        main()
      {
        using namespace std;
      
        Child c;
      }
      
      /* stdout
      Mother construction
      Child construction
      */
    • 부모 클래스의 생성자를 명시하지 않아도 부모 클래스의 기본 생성자가 호출된다.

      • 다음 예제는 부모 클래스의 기본 생성자가 존재하지 않아서 에러가 발생한다.
      #include <iostream>
      
      class Mother
      {
          int i_;
      
      public:
          Mother(const int& i_in)
              : i_(i_in)
          {
              std::cout << "Mother construction\n";
          }
      };
      
      class Child : public Mother
      {
          double d_;
      
      public:
          Child()
              : d_(1.0)
          {
              std::cout << "Child construction\n";
          }
      };
      
      int        main()
      {
          using namespace std;
      
          Child c;
      }
      error C2512: 'Mother': no appropriate default constructor available

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

C++ 상속과 패딩  (0) 2021.03.20
C++ 유도된 클래스들의 소멸 순서  (0) 2021.03.19
C++ 상속 Teacher-Student 예제  (0) 2021.03.19
C++ 상속 기본 예제  (0) 2021.03.19
C++ 상속 (Inheritance)  (0) 2021.03.19
C++/Class 2021. 3. 19. 19:47

상속 Teacher-Student 예제

main.cpp

#include "Student.h"
#include "Teacher.h"

int        main()
{
    using namespace std;

    Student std("Jack Jack");

    std.setName("New Jack");
    std.getName();

    cout << std.getName() << endl;

    Teacher teacher1("Dash");

    teacher1.setName("Dr. K");

    cout << teacher1.getName() << endl;

    cout << std << endl;
    cout << teacher1 << endl;
}

/* stdout
New Jack
Dr. K
New Jack 0
Dr. K
*/

Person.h

#pragma once

#include <iostream>

class Person
{
    std::string name_;

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

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

    void    setName(const std::string& name_in)
    {
        name_ = name_in;
    }
};

Student.h

#pragma once

#include "Person.h"

class Student : public Person
{
    int        intel_;

public:
    Student(const std::string& name_in = "No Name", const int& intel_in = 0)
        : Person(name_in), intel_{ intel_in }
    {}

    void    setIntel(const int& intel_in)
    {
        intel_ = intel_in;
    }

    int        getIntel()
    {
        return intel_;
    }

    friend std::ostream& operator << (std::ostream& out, const Student& student)
    {
        out << student.getName() << ' ' << student.intel_;
        return out;
    }
};

Teacher.h

#pragma once

#include "Person.h"

class Teacher : public Person
{

public:
    Teacher(const std::string& name_in = "No Name")
        : Person(name_in)
    {}

    friend std::ostream& operator << (std::ostream& out, const Teacher& teacher)
    {
        out << teacher.getName();
        return out;
    }
};

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

C++ 유도된 클래스들의 소멸 순서  (0) 2021.03.19
C++ 유도된 클래스들의 생성 순서  (0) 2021.03.19
C++ 상속 기본 예제  (0) 2021.03.19
C++ 상속 (Inheritance)  (0) 2021.03.19
C++ 의존 관계 (Dependency)  (0) 2021.03.19
C++/Class 2021. 3. 19. 19:45

상속 기본 예제

  • 상속의 본질은 일반화이다.

  • 용어

    • Generalized class : 상속하는 상위 클래스

    • Derived class : 상속을 받은 하위 클래스

  • 예제

    #include <iostream>
    
    using namespace std;
    
    class Mother
    {
      int    i_;
    
    public:
      Mother(const int& i_in = 0)
        : i_{ i_in }
      {
        cout << "Mother Constructor\n";
      }
    
      void        setValue(const int& i_in)
      {
        i_ = i_in;
      }
    
      int        getValue()
      {
        return i_;
      }
    };
    
    class Child : public Mother
    {
      double    d_;
    
    public:
      Child(const int & i_in = 0, const double & d_in = 0)
        //: i_{i_in}, d_{d_in}  // 생성자 호출 순서때문에 불가능
      {
        cout << "Child Constructor\n";
        Mother::setValue(i_in);
        d_ = d_in;
      }
    
      void        setValue(const int& i_in, const double& d_in)
      {
        Mother::setValue(i_in);
        d_ = d_in;
      }
    
      void        setValue(const double& d_in)
      {
        d_ = d_in;
      }
    
      double    getValue()
      {
        return d_;
      }
    };
    
    class Daughter : public Mother  // 여러 클래스로 상속할 수 있다.
    {
    
    };
    
    class Son : public Mother
    {
    
    };
    
    int        main()
    {
      Mother mother(120);
      mother.setValue(1024);
      cout << mother.getValue() << endl;
    
      Child child;
      child.Mother::setValue(987);
      cout << child.Mother::getValue() << endl;
    
      child.setValue(128);
      cout << child.getValue() << endl;
    }
    
    /* stdout
    Mother Constructor
    1024
    Mother Constructor
    Child Constructor
    987
    128
    */
    • 중간 주석 부분을 제거하고 빌드해보면 i_{i_in}에서 오류가 발생한다.

      error C2614: 'Child': illegal member initialization: 'i_' is not a base or member
      • VS에서 마우스를 갖다대면 inaccessible 상태로 나오지만, 접근 지정자 문제는 아니다.

      • 부모 클래스의 생성자를 호출하기 전에 i_ 변수에 접근하려해서 발생하는 오류라고 한다.

      • 이를 해결할 때는 아래와 같이 부모 클래스의 생성자를 활용하는 편인 것 같다.


  • 다음과 같이 부모 클래스의 생성자를 위임 생성자처럼 사용하는게 정석같은 느낌이다.

    #include <iostream>
    
    using namespace std;
    
    class Mother
    {
      int    i_;
    
    public:
      Mother(const int& i_in = 0)
        : i_{ i_in }
      {
        cout << "Mother Constructor\n";
      }
    
      void    setValue(const int& i_in)
      {
        i_ = i_in;
      }
    
      int        getValue()
      {
        return i_;
      }
    };
    
    class Child : public Mother
    {
      double    d_;
    
    public:
      Child(const int & i_in = 0, const double & d_in = 0)
        : Mother(i_in), d_{d_in}
      {
        cout << "Child Constructor\n";
      }
    
      void    setValue(const int& i_in, const double& d_in)
      {
        Mother::setValue(i_in);
        d_ = d_in;
      }
    
      void    setValue(const double& d_in)
      {
        d_ = d_in;
      }
    
      double    getValue()
      {
        return d_;
      }
    };
    
    int        main()
    {
      Mother mother(120);
      mother.setValue(1024);
      cout << mother.getValue() << endl;
    
      Child child;
      child.Mother::setValue(987);
      cout << child.Mother::getValue() << endl;
    
      child.setValue(128);
      cout << child.getValue() << endl;
    }
    
    /* stdout
    Mother Constructor
    1024
    Mother Constructor
    Child Constructor
    987
    128
    */

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

C++ 유도된 클래스들의 생성 순서  (0) 2021.03.19
C++ 상속 Teacher-Student 예제  (0) 2021.03.19
C++ 상속 (Inheritance)  (0) 2021.03.19
C++ 의존 관계 (Dependency)  (0) 2021.03.19
C++ 연계, 제휴 관계 (Association)  (0) 2021.03.19
C++/Class 2021. 3. 19. 19:44

상속 (Inheritance)

  • is-a relationship

기본 예제

상속 Teacher-Student 예제

유도된 클래스들의 생성 순서

유도된 클래스들의 소멸 순서

상속과 패딩

상속과 접근 지정자

함수 오버라이딩

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

다중 상속

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

C++ 상속 Teacher-Student 예제  (0) 2021.03.19
C++ 상속 기본 예제  (0) 2021.03.19
C++ 의존 관계 (Dependency)  (0) 2021.03.19
C++ 연계, 제휴 관계 (Association)  (0) 2021.03.19
C++ 구성 관계 (Composition Relationship)  (0) 2021.03.19
C++/Class 2021. 3. 19. 19:40

의존 관계 (Dependency)

  • Worker 클래스에서 잠깐 사용하는 Timer 클래스

    main.cpp

    #include "Timer.h"
    #include "Worker.h"
    
    int        main()
    {
      Worker().doSomething();
    }
    
    /* stdout
    4e-07
    */

    Timer.h

    #pragma once
    #include <iostream>
    #include <chrono>
    
    using namespace std;
    
    class Timer
    {
        using clock_t = std::chrono::high_resolution_clock;
        using second_t = std::chrono::duration<double, std::ratio<1>>;
    
        std::chrono::time_point<clock_t> start_time = clock_t::now();
    
    public:
        void elapsed()
        {
            std::chrono::time_point<clock_t> end_time = clock_t::now();
    
            cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
        }
    };

    Worker.h

    #pragma once
    
    class Worker
    {
    public:
      void    doSomething();
    };

    Worker.cpp

    #include "Worker.h"
    #include "Timer.h"
    
    void    Worker::doSomething()
    {
      Timer timer;
    
      // do some work here
    
      timer.elapsed();
    }

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

C++ 상속 기본 예제  (0) 2021.03.19
C++ 상속 (Inheritance)  (0) 2021.03.19
C++ 연계, 제휴 관계 (Association)  (0) 2021.03.19
C++ 구성 관계 (Composition Relationship)  (0) 2021.03.19
C++ 객체들의 관계 (Object Relationship)  (0) 2021.03.19
C++/Class 2021. 3. 19. 19:40

연계, 제휴 관계 (Association)

  • 의사 클래스와 환자 클래스로 비유

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Doctor;
    
    class Patient
    {
      string            name_;
      vector<Doctor*>    doctors_;
    
    public:
      Patient(const string &name_in)
        : name_{name_in}
      {}
    
      void    addDoctor(Doctor* new_doctor)
      {
        doctors_.push_back(new_doctor);
      }
    
      void    meetDoctors();
    
      friend class Doctor;
    };
    
    class Doctor
    {
      string                name_;
      vector<Patient*>    patients_;
    
    public:
      Doctor(const string &name_in)
        : name_{name_in}
      {}
    
      void    addPatient(Patient* new_patient)
      {
        patients_.push_back(new_patient);
      }
    
      void    meetPatients()
      {
        for (auto& e : patients_)
          cout << "Meet patient : " << e->name_ << '\n';
      }
    
      friend class Patient;
    };
    
    void    Patient::meetDoctors()
    {
      for (auto& e : doctors_)
        cout << "Meet doctor : " << e->name_ << '\n';
    }
    
    int        main()
    {
      Patient* p1 = new Patient("Jack Jack");
      Patient* p2 = new Patient("Dash");
      Patient* p3 = new Patient("Violet");
    
      Doctor* d1 = new Doctor("Doctor K");
      Doctor* d2 = new Doctor("Doctor L");
    
      p1->addDoctor(d1);
      d1->addPatient(p1);
    
      p2->addDoctor(d2);
      d2->addPatient(p2);
    
      p2->addDoctor(d1);
      d1->addPatient(p2);
    
      cout << "p1\n";
      p1->meetDoctors();
    
      cout << "\nd1\n";
      d1->meetPatients();
    
      delete p1, p2, p3, d1, d2;
    
    }
    
    /* stdout
    p1
    Meet doctor : Doctor K
    
    d1
    Meet patient : Jack Jack
    Meet patient : Dash
    */

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

C++ 상속 (Inheritance)  (0) 2021.03.19
C++ 의존 관계 (Dependency)  (0) 2021.03.19
C++ 구성 관계 (Composition Relationship)  (0) 2021.03.19
C++ 객체들의 관계 (Object Relationship)  (0) 2021.03.19
C++ Nested Types  (0) 2021.03.16
C++/Class 2021. 3. 19. 19:37

구성 관계 (Composition Relationship)

  • 몬스터 클래스와 그의 멤버인 좌표 클래스로 비유했다.

  • Monster 클래스의 멤버로 존재하는 Position2D 클래스 인스턴스 location_ (Monster.h 참고)

    • location_ 인스턴스는 Monster 클래스로 만들어진 mon1 인스턴스의 Part-of라고 할 수 있다.

    • mon1이 소멸하면 자동으로 location_도 소멸한다.

    main.cpp

    #include "Monster.h"
    
    int        main()
    {
        using namespace std;
    
        Monster mon1("Sanson", Position2D(0, 0));
    
        cout << mon1 << endl;
        {
            mon1.moveTo(Position2D(1, 1));
            cout << mon1 << endl;
        }
    }
    
    /* stdout
    Sanson 0 0
    Sanson 1 1
    */

    Monster.h

    #pragma once
    #include "Position2D.h"
    
    class Monster
    {
      std::string   name_;
      Position2D    location_;
    
    public:
      Monster(const std::string name_in, const Position2D & pos_in)
        : name_{name_in}, location_{pos_in}
      {}
    
      void    moveTo(const Position2D& pos_target)
      {
        location_.set(pos_target);
      }
    
      friend std::ostream& operator << (std::ostream& out, const Monster& monster)
      {
        out << monster.name_ << ' ' << monster.location_;
        return (out);
      }
    };

    Position2D.h

    #pragma once
    
    #include <iostream>
    
    class Position2D
    {
      int    x_;
      int    y_;
    
    public:
      Position2D(const int &x_in, const int &y_in)
        : x_(x_in), y_(y_in)
      {}
    
      void    set(const Position2D& pos_target)
      {
        set(pos_target.x_, pos_target.y_);
      }
    
      void    set(const int& x_target, const int& y_target)
      {
        x_ = x_target;
        y_ = y_target;
      }
    
      friend std::ostream& operator << (std::ostream& out, const Position2D& pos)
      {
        out << pos.x_ << ' ' << pos.y_;
        return (out);
      }
    };

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

C++ 의존 관계 (Dependency)  (0) 2021.03.19
C++ 연계, 제휴 관계 (Association)  (0) 2021.03.19
C++ 객체들의 관계 (Object Relationship)  (0) 2021.03.19
C++ Nested Types  (0) 2021.03.16
C++ 익명 객체 (Anonymous Class)  (0) 2021.03.16
C++/Class 2021. 3. 19. 19:37

객체들의 관계 (Object Relationship)

관계 관계를 표현하는 동사 예시
구성 (Composition) Part-of 두뇌육체의 일부이다
집합 (Aggregation) Has-a 어떤 사람자동차를 가지고 있다.
연계 (Association) Uses-a 환자의사의 치료를 받는다.
의사환자들로부터 치료비를 받는다.
의존 (Dependency) Depends-on 목발을 짚었다.

관계 관계의 형태 다른 클래스에도 속할 수 있는가 멤버의 존재를 클래스가 관리하는가 방향성
구성 (Composition) 전체/부품 No Yes 단방향
집합 (Aggregation) 전체/부품 Yes No 단방향
연계 (Association) 용도 외 무관 Yes No 단방향, 양방향
의존 (Dependency) 용도 외 무관 Yes Yes 단방향

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

C++ 연계, 제휴 관계 (Association)  (0) 2021.03.19
C++ 구성 관계 (Composition Relationship)  (0) 2021.03.19
C++ Nested Types  (0) 2021.03.16
C++ 익명 객체 (Anonymous Class)  (0) 2021.03.16
C++ friend  (0) 2021.03.16