Coding/Unreal, C++

[C++ Primer Plus] 14. Reusing Code in C++

대답해야 할 질문들

  • has-a 관계를 가지는 두 가지 방법은 무엇인가?
  • 다이아몬드 상속을 할 때, 신경써줘야하는 부분은 무엇인가?
  • template class에서 implicit/explicit instantiation과 specialization의 문법은 무엇인가?

Classes with Object Members

  • 클래스가 오브젝트 멤버를 가지는 방법은 2가지다.
    1. 오브젝트를 변수로 선언한다. (containment)
    2. protected or private으로 상속한다.

Private Inheritance

  • 상속할 때, Private으로 상속하면 base-class의 public 멤버와 protected 멤버가 derived-class의 private 멤버가 된다.
  • 즉, 인터페이스가 똑같이 이어지는 게 아니기 때문에 Is-a 관계가 아니라 Has-a 관계다.
  • 예를 들어, Student 클래스가 std::string 클래스를 private 상속받으면 std::string처럼 문자열을 저장할 수 있다.

Initialization Base-Class Components

  • Containment의 생성자(std::string)와 Private 상속의 생성자의 생김새 차이를 유의하자.
// Containment
Student(const char * str, const double * pd, int n)
	: name(str), scores(pd, n) {}
// Private Inheritance
Student(const char * str, const double * pd, int n)
	: std::string(str), ArrayDb(pd, n) {}

Accessing Base-Class Method

  • Base-Class의 메소드에 접근하려면 scope-resolution operator(::)를 써야 한다.
double Student::Average() const
{
	if (ArrayDb::size() > 0)
    	return ArrayDb::sum()/ArrayDb::size();
}
  • 만약 Base-Class 오브젝트의 주소값 자체에 접근하려면 어떻게 하면 좋을까? 답은 형변환이다.
ostream& operator<<(ostream& os, const Student& stu)
{
	os << "Scores for " << (const String &) stu << ":\n";
}

Containment or Private Inheritance?

  • 그렇다면 containment와 Private Inheritance 중에 뭘 사용하는 게 좋을까?
  • 결론부터 말하자면 특별한 이유가 없으면 containment다.
  • private Inheritance를 써야되는 경우는 다음과 같다.
    1. protected 멤버에 접근해야 하는 경우
    2. 해당 클래스의 virtual function을 수정하고 싶은 경우

Redefining Access with using

  • private instance로 has-a 관계를 만들었다면 base-class의 함수들이 전부 감춰지므로 래핑 함수를 만들게 되는 경우가 있다. 예를 들면 다음과 같은 함수가 생기는 것이다.
double Student::sum() const // public Student's method
{
	return std::valarray<double>::sum();
}
  • 이 경우엔 using 문법을 사용해서 쉽게 선언할 수 있다.
class Student: private std::string, private std::valarray<double>
{
public:
	using std::valarray<double>::min;
    using std::valarray<double>::max;
    ...
}
  • 이전엔 using 키워드를 쓰지 않아도 됐었는데, 이는 deprecated 문법이다.

Multiple Inheritance

  • 다중상속은 다음과 같은 문법이다. 각자마다 public 한정자를 줘야하는 걸 주목하자. 안주면 private된다.
class SingingWaiter : public Waiter, public Singer {...}
  • 문제는 Waiter와 Singer가 같은 base-class를 두는 경우다. 따로 처리를 안해주면 Waiter의 Worker 오브젝트 하나, Singer의 Worker 오브젝트 하나, 이렇게 두 개 오브젝트가 생긴다. 이런 경우가 의도된 것일 수 있는데, 보통 그렇진 않으므로 이 경우엔 어떻게 처리해야 하는지 알아보자.
  • 위 문제를 해결하려면 Waiter와 Singer가 전부 virtual 상속을 해줘야 한다.
class Singer : virtual public Worker {...};
class Waiter : public virtual Worker {...};
  • virtual? 동적 바인딩이기 때문인가? 라고 생각하기 쉽지만 절대 그런게 아니다. runtime에 상속 판단이 되는게 아니다. virtual인 이유는 좀더 정치적인 이유에 가깝다. C++ 커뮤니티가 새로운 키워드 추가에 매우 회의적이었기 때문에 이미 있는 키워드를 썼던 것이다.

New Constructor Rules

  • MI는 생성자에서도 신경써줘야 하는 부분이 있다.
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
	: Waiter(wk, p), Singer(wk, v) // flawed
  • 위 코드는 제대로 작동하지 않는다. Waiter와 Singer 클래스의 생성자가 내부에서 base-class의 생성자를 호출할텐데, 이게 작동하지 않기 때문이다. 그 이유는 virtual 상속인 경우, 컴파일러가 해당 호출을 무효로 처리하기 때문이다. 그래서 따로 base-class의 생성자를 호출해줘야 한다.
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
	: Worker(wk), Waiter(wk, p), Singer(wk, v)

Which Method?

  • MI는 base-class들의 함수를 호출할 때에 주의해야 한다. 함수 이름이 겹치는 경우가 많기 때문이다. scope resolution operator를 사용해서 정확히 어떤 base class의 함수를 쓸건지 말해줘야 한다.
newhire.Singer::Show();

Mixed Virtual and Nonvirtual Bases

  • 네 개의 클래스를 상속받는데, 두 개가 virtual 상속이고 두 개가 일반 상속이면 총 몇개의 base 클래스가 생길까?
  • 정답은 3개다. virtual 끼리 하나, 일반 상속이 하나씩해서 3개다. virtual 끼리는 하나를 공유한다고 생각하면 쉽다.

Class Templates

  • Class에서 Template을 적용하려면 다음 문법이다.
template<typename Type>
class Stack
{
private:
	...
    Type items[MAX];
    ...
};
  • 멤버 함수 구현부에도 template 키워드를 써줘야 한다.
template <class Type>
bool Stack<Type>::push(const Type & item)
{
...
}
  • Implicit Instantiation, explicit Instantiation, explicit specialization은 template 함수와 유사하다.
ArrayTP<int, 100> stuff; // implicit instantiation
template class ArrayTP<int, 100>; // explicit instantiation

template <>
class SortedArray<const char *>
{
...
};
  • explicit specialization에서는 partial specialization도 가능하다.
template<class T1, class T2> class Pair {...};
template<class T1> class Pair<T1, int> {...}; // partial specialization
template<> class Pair<int, int> {...}; // specialization

References

  • C++ Primer Plus 6th, Chapter 14