Ch14 상속, 템플릿

25 분 소요

멤버 초기화

struct AAA {
	AAA() : a(10), c(12)  // 초기화 리스트 사용. 
	{
		b = 10;  // 생성자 내에서 사용
	}

	int a, b;
	int c = 10; // 선언과 동시에 초기화. 근데 초기화리스트에 있어서 씹힘
};
  • (선언과 동시에 초기화 == 초기화 리스트) 후에 생성자 블록에 들어가게 됨.
  • 위 2개가 다른점이 굳이 있다면 둘다 적용시 초기화 리스트가 적용된다는 것.
  • 초기화 리스트는 리스트 순서가 아니라 선언된 순서대로 초기화 함.
  • 초기화 리스트에선 아래에 설명할 가상 기초 클래스를 제외하면 직계 부모의 생성자만 호출 할 수 있음.
  • 초기화 리스트에서 직계부모의 생성자를 안적으면 기본 생성자를 호출함.

상속

Has-A? Is-A?

struct AAA { 
	void Do() {}
};
struct BBB : public AAA            // is-a
{ 
	// void Do() 안해도 외부에서 호출가능
};
struct CCC {                       // has-A
	void Do() { aaa.Do(); }
private:
	AAA aaa;
};
struct DDD : private AAA          // has-a
{ 
	void Do() { AAA::Do(); } 
};

모두 AAA 의 코드를 재사용함. 하지만 접근법에서 차이를 보임.

BBB 는 상속받아서 부모의 구현을 들고오는 Is-A 관계임.

CCC 는 멤버변수로 둬서(Containment) 그것의 메소드를 호출하는 Has-A 관계임.

DDD 는 부모가 private 이라서 Do 를 외부에서 호출하려면 내부에서 써줘야하는 Has-A 관계임.

근데 이거 구분 큰 의미없다는 걸 읽은 적이 있음.

Private 상속과 Has-A

struct AAA {
	void Do() { cout << name << name.size() << endl; }
	string_view Get() { return name; }
private:
	string name = "asdf";
};

struct BBB : private string
{
	BBB() : string("asfd") {}
	void Do() { cout << *this << size() << endl; }
	string_view Get() { return *this; }
};

전자는 컨테인먼트를 후자는 private 상속을 이용한 Has-A 구현임

  • namesize() 의 출력,
  • string 을 가져오는 방법
  • string 을 초기화하는 방법

의 차이점을 주의해서 보면 됨.

보통 전자가 쉬워서 많이 쓰이임

하지만 가상함수 재정의나, 들고오려는 객체의 protected 된 함수나 변수를 쓰고 싶으면, 즉 상속의 기능도 필요하면 private 상속이 필수가 됨.

protected 상속도 있는데 이건 다음 표를 참조.

특성 public 상속 protected 상속 private 상속
public 멤버 파생클래스의 public 멤버 파생클래스의 protected 멤버 파생클래스의 private 멤버
protected 멤버 파생클래스의 protected 멤버 파생클래스의 protected 멤버 파생클래스의 private 멤버
private 멤버 접근 x 접근 x 접근 x

접근자 재정의

struct AAA {
	string name = "asdf";
	void Do() { cout << name << endl; }
};

struct BBB : private AAA
{
public:
	using AAA::name;
	using AAA::Do;
};

원하는 접근자에서 using 을 위 코드처럼 하면 됨.

위 예시에선 public 으로 바뀌어서 외부에서 호출 가능한 상태가 됨.

using 없이 해도 되는데 적는게 권고됨.

다중상속(MI : multiple inheritance)

부모가 공통 조상을 가지고 있으면 공통 조상이 여러개가 있게되어 Ambious 컴파일 에러같은게 터지고 그럼.

클래스 내에서 부모클래스을 AAA:: 이런식으로 한정해서 쓰면 어찌 될수도 있음. 하지만 클래스 외부에서 공통조상의 함수를 호출할 땐 그것도 안먹힘.

그래서 나오는게 가상 기초 클래스(Virtual Base Class)

struct Common { 
	int a = 10;
	Common(int a) : a(a) {}
	//void Do() { cout << a << endl; }
	void Do() { cout << a << endl; }
};
struct AAA : protected virtual Common { 
	AAA(int a) : Common(a) {}
	void Do() { cout << a * 10 << endl; };
};
struct BBB : private virtual Common {  
	BBB(int a) : Common(a) {}
	//void Do() { cout << a << endl; }
};
struct CCC : public AAA, BBB
{
	CCC(int a) : Common(a), AAA(a), BBB(a) {}
};

void main()
{
	CCC c(10);
	c.Do();
}
  1. AAA, BBB 의 부모가 virtual 로 선언 되어 있음. 중복상속이 예상되는 부모는 virual 키워드를 붙여서 하나의 공통조상만 가능하게 함

  2. CCC 의 생성자에서 가상 기초 클래스를 초기화 함. 그럼 BBB 생성자에 있는 Common 은 어떻게 되는가? 중간 클래스에서 기초생성자의 초기화는 씹힘.

  3. 가상 기초 클래스는 적어도 하나 이상이 protected 이상의 접근자로 있어야함. 예를들어서 CCC 에서 Common 을 초기화하려는데 AAA, BBB 모두 private 으로 Common 을 상속받으면 안됨.

  4. 함수 재정의를 AAA, BBB 둘다하면 Ambigious 하다고 에러뜸.(가상함수도 그럼)

  5. 만약 virtual 안붙인 Common 을 상속받는 DDD 가 있어서 CDDD 도 상속받게 되면 가상 기초 클래스 하나와 그냥 클래스 하나가 CCC 에 공존하게 됨. 즉 같은 클래스라도 가상 기초 클래스로 상속받은건 한개만 남고 아닌건 상속받은 횟수만큼 존재하게 됨.

1번 2번 5번만 기억하고 나머진 컴파일러가 잡아주니 그렇구나 하고 ㄱㄱ

클래스 템플릿

typename, decltype

template<class T> 이거도 있고 template<typename T> 이거도 있는데 T 자리에 class 만 들어가는게 아니라서 후자가 맞고 최신버전임.

책엔 이 챕터에 없긴 한데 메모용

수식 매개변수

template <class _Ty, size_t _Size>
class array { // fixed size array of values

위에서 size_t _Size 가 수식 매개 변수임.

이를 통해 array 의 공간을 _Size 만큼 정적으로 할당하게 됨.

그리고 당연하겠지만 size 가 다른 클래스는 다 다른 클래스임.

template<double* n>
class AAA
{
public:
	double Do() { return *n; }
};

double  pi = 3.14;
constexpr double* pi_p = &pi;

void main()
{
	AAA<pi_p> b;
	pi = 32;
	cout << b.Do();
}
32

수식 매개변수는 정수, 열거, 참고, 포인터 자료형만 가능하며 constexpr 만 가능함

위 코드는 double* 가 수식 매개변수로 어떻게 들어가는지를 보여줌.

특수화

암시적 구체화 (implicit Instaniation)

template 은 작성한다고 컴파일러가 그 클래스를 만드는게 아님. 타입을 지정해서 변수를 만들거나 메소드를 쓰게 되면 (ex. vector<int> a;) 타입에 맞추어 그 클래스나 메소드를 컴파일러가 생성함. 이를 Instaniation 이라고 함.

명시적 구체화

위에 vector<int>; 이렇게 써놓으면 vector<int> 타입 변수를 안쓰더라도 컴파일러가 클래스를 생성함. 함수도 마찬가지.

명시적 특수화 (Explicit Specialization)

template<> class AAA<int> { ... }; 이런식으로 특정 자료형에 맞추어서 정의까지 갈아엎는 경우를 말함.

부분적 특수화 (Partial Specialization)

여러 데이터형 매개변수가 있는데 일부만 특수화할 경우 partial specialization 이라고 부름.

인자가 적용 가능한 후보가 여러개 있는 경우 가장 특수화된 템플릿을 따르게 됨

템플릿과 Friend

template<typename T1, typename T2>
// 얘도 AAA 외부에 있던거라 암시적, 명시적 특수화는 friend 를 안먹음
void Do2(T1 in, T2 a) { cout << in << a.a << endl; }

template<typename T>
class AAA
{
	// Template 이 아닌 Friend.
	friend void Do1(T in, AAA<T> a) { cout << in << a.a << endl; }

	// AAA 에 Bound 된 Template 인 friend.
	friend void Do2<>(T in, AAA<T> a);
	
	// AAA 에 Unbound 된 Template 인 friend. 
	// AAA 는 T 인데 아래 얘들은 T 에 종속되어 있지 않아서 Unbound 임
	template<typename T1, typename T2>
	friend void Do3(T1 in, T2 a);

private:
	T a = { 'T' };
};

//  AAA 의 외부에서 특수화를 하므로 friend 를 안먹음
//void Do1(string in, AAA<float> a) { cout << in << a.a << endl; }

template<typename T1, typename T2>
inline void Do3(T1 in, T2 a)
{
	cout << in << a.a << endl;
}

int main()
{
	Do1("asdf", AAA<string>());
	Do2(string("asff"), AAA<string>());
	Do3("asdf", AAA<string>());  
	//Do1("asdf", AAA<float>());    //  
	//Do2("asdf", AAA<float>());    //  Do2<string, AAA<float>>() 는 외부에 정의되어서 friend 가 아니라 안됨.	
	//Do3("asdf", AAA<float>());
	Do3("asdf", AAA<float>());
}

AAA 클래스가 특수화 될 때 사용되는 타입에는 AAA 함수 내부에 있는 friend 가 다 적용됨. 하지만 다른 타입에 대해선 friend 가 적용되는가? 이게 핵심임.

결론부터 말하면 Do3AAA 클래스의 템플릿에 종속적이지 않게 friend 를 걸어줌. 즉 타입에 자유로움.

그 차이는 Do3 만이 "asfd", AAA<float>() 의 인자를 받아드릴 수 있음이 잘 보여줌. Do1, Do2 로는 (const char*, AAA<float>) 을 인자로 가지면서 __ AAA 와 friend 가 동시에 안됨.__

  • 템플릿이 아닌 Friend
    • Do1 가 정의될 때 template 이 없음. 그냥 인자가 템플릿일 뿐임
    • AAA 가 특정 타입에 특수화가 될 때마다 그 타입에 대해서 friend 선언이 됨.
    • 하지만 템플릿이 아니므로 명시적 특수화를 꼭 해줘야 쓸 수 있음.
    • 다른 타입으로 명시적 특수화를 할 땐 AAA 밖이라서 이건 friend 선언이 안먹힘.
  • Bound Template Frient
    • 얘도 비슷하게 AAA가 특수화 될 때 적용되는 타입만 friend 선언이 됨.
    • Do2의 암시적 / 명시적 구체화는 AAA 클래스 밖이라서 friend 선언이 안먹힘
  • Unbound Template Friend
    • 함수의 template 으로 오는 타입이 AAA 에서 쓰는 타입과 독립적임
    • 그래서 AAA 함수 외부에서 직접 명시적 특수화를 하는게 아닌 이상
    • 구체화는 모두 AAA 의 내에서 friend 선언이 되므로 타입에 자유로움
(참고) friend 에 대해서

friend 함수는 클래스의 메소드가 아님.

따라서 friend 함수에 대해선 private, public, protected 는 적용이 안됨.

class 내부에서 정의되었다고 AAA::FriendFunc() 이런식으로 namespace 붙이는것도 아님.

댓글남기기