일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- lua
- exception
- 암시적 변환
- 언리얼
- Effective c++
- operator new
- 루아
- 비교 함수 객체
- 오블완
- 스마트 포인터
- effective stl
- 게임
- Smart Pointer
- resource management class
- 함수 객체
- 영화 리뷰
- 예외
- 영화
- virtual function
- more effective c++
- implicit conversion
- 다형성
- 상속
- reference
- c++
- UE4
- 참조자
- 티스토리챌린지
- 반복자
- 메타테이블
Archives
- Today
- Total
스토리텔링 개발자
[More Effective C++] 25. 함수를 가상 함수처럼 만들기 본문
728x90
항목 25. 생성자 함수와 비멤버 함수를 가상 함수처럼 만드는 방법
가상 생성자(virtual constructor) : 생성자 함수를 가상 함수처럼
- 런타임에 타입을 체크하겠다(가상) + 명확한 타입의 객체에 대한 메모리 할당(생성자) 가 어떻게 양립 가능할까?
- 하지만 가능한 일이다. 게다가 비일비재하게 사용한다.
class NLComponent // 추상 기본 클래스
{
public:
... // 순수 가상 함수가 최소한 하나는 있다.
};
// 순수 가상 함수가 없는 하위 클래스들
class TextBlock : public NLComponent { ... };
class Graphic : public NLComponent { ... };
class NewsLetter // 뉴스레터
{
public:
NewsLetter(istream& str); // 디스크 데이터를 읽기 위한 istream
...
private:
// 디스크 데이터를 NLComponent로 할당해주는 함수
// 이 함수는 생성자처럼 동작하며,
// 런타임에 디스크에서 읽은 정보에 따라 다른 타입의 객체를 만든다.
// 즉 이 함수를 가상 생성자(virtual constructor)라 부를 수 있다.
static NLComponent* readComponent(istream& str);
private:
// NLComponent 객체를 리스트로 관리한다.
list<NLComponent*> component;
};
NewsLetter::NewsLetter(istream& str)
{
while(str)
{
// 객체를 생성하여 뉴스레터 구성요소 리스트에 추가
components.push_back(readComponent(str));
}
}
- 가상 복사 생성자(virtual copy constructor)
- 가상 생성자 중 특히 널리 쓰이는 함수
- 함수를 호출한 객체를 그대로 본뜬 사본의 포인터를 반환한다.
- 복사 생성자를 그대로 이용하여 구현하기 때문에, 유지보수에도 편리하다.
- 가상 함수에 대한 완화된 규칙이 사용된다.
- 기본 클래스의 가상 함수를 재정의한 파생 클래스의 가상 함수는 똑같은 반환 타입이 아니어도 된다.
- 가상 복사 생성자를 사용한 뉴스레터
class NLComponent
{
public:
NewsLetter(const NewsLetter& rhs);
// 가상 복사 생성자
// 해당 클래스의 진짜 복사 생성자를 호출해주는 것 뿐이지만
// 가상 함수(clone)를 통해 복사를 진행한다.
virtual NLComponent* clone() const = 0;
...
};
class TextBlock : public NLComponent
{
public:
// 반환값이 기본 클래스 버전과 다르다.
virtual TextBlock* clone() const
{
return new TextBlock(*this);
}
...
};
class Graphic : public NLComponent
{
public:
virtual Graphic* clone() const
{
return new Graphic(*this);
}
...
};
NewsLetter::NewsLetter(const NewsLetter& rhs)
{
for(list<NLComponent*>::const_iterator it = rhs.components.begin() ;
it != rhs.components.end() ; ++it)
{
components.push_back((*it)->clone());
}
}
비멤버 함수를 가상 함수처럼
- 멤버 함수를 가상 함수로 선언하여 처리하기 힘든 경우가 있다.
- 예컨대 아래의 경우, 가독성에 심각한 문제가 생긴다.
class NLComponent
{
public:
// 출력 연산자 재구현을 위한 순수 가상 함수화
// 하지만...
virtual ostream& operator<<(ostream& str) const = 0;
...
};
class TextBlock : public NLComponent
{
public:
virtual ostream& operator<<(ostream& str) const;
};
class Graphic : public NLComponent
{
public:
virtual ostream& operator<<(ostream& str) const;
};
TextBlock t;
Graphic g;
// 다음 코드는 어떻게 봐도 cout으로 출력하는 것으로 보이지 않는다..
t << cout;
g << cout;
- 출력 연산자를 대신해 커스텀 가상 함수로 구현할 수는 있겠지만..
class NLComponent
{
public:
virtual ostream& print(ostream& s) const = 0;
...
}
class TextBlock : public NLComponent
{
public:
virtual ostream& print(ostream& s) const;
...
};
class Graphic : public NLComponent
{
public:
virtual ostream& print(ostream& s) const;
...
};
TextBlock t;
Graphic g;
// 일단 보기엔 나아졌지만, 다른 객체들과의 통일성이 깨진다.
t.print(cout);
g.print(cout);
- 다른 타입에 대해서는 operator<<를 사용하는데 NLComponent 하위 객체만 print를 사용한다?
- 썩 만족스러운 결론은 아니다.
- 이 경우 비멤버 함수로 operator<<를 빼내고, 내부 구현은 print 함수를 재활용하면 모든 것이 만족스러워진다.
// operator<< -> print 로 함수가 두 번 호출되는 비용이 싫으므로 인라인 선언
inline ostream& operator<<(ostream& s, const NLComponent& c)
{
return c.print(s);
}
TextBlock t;
Graphic g;
// 이제 기대하던 바대로 사용할 수 있게 되었다.
cout << t;
cout << g;
번외
- 두 개 이상의 인자에 대해서도 가상 함수처럼 동작하게 할 수 있지 않을까?(항목 31 참조)
728x90
'개발 > More Effective C++' 카테고리의 다른 글
[More Effective C++] 27. 힙 전용, 힙 불가 클래스 만들기 (1) | 2024.09.06 |
---|---|
[More Effective C++] 26. 클래스 인스턴스 개수 제한 (0) | 2024.09.05 |
[More Effective C++] 24. 다형성의 비용 (0) | 2024.09.03 |
[More Effective C++] 23. 적절한 라이브러리 선택하기 (0) | 2024.09.02 |
[More Effective C++] 22. 대입 형태 연산자 선호하기 (0) | 2024.08.30 |
Comments