일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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
- 메타테이블
- 루아
- Smart Pointer
- 참조자
- 비교 함수 객체
- 오블완
- 스마트 포인터
- effective stl
- 영화
- 함수 객체
- implicit conversion
- 상속
- Effective c++
- 암시적 변환
- reference
- exception
- 예외
- 언리얼
- 반복자
- more effective c++
- virtual function
- 영화 리뷰
- 다형성
- 티스토리챌린지
- c++
- operator new
- UE4
- resource management class
- lua
- 게임
Archives
- Today
- Total
스토리텔링 개발자
[More Effective C++] 26. 클래스 인스턴스 개수 제한 본문
728x90
항목 26. 클래스 인스턴스의 개수를 의도대로 제한하는 방법
개수 제한의 예시
- 프린터. 프린터 객체는 갯수 제한을 할 수밖에 없을 것이다.
객체 개수를 0개로 제한
- 클래스의 생성자를 private 선언한다.
class CanBeInstantiated
{
private:
CanBeInstantiated();
CanBeInstantiated(const CantBeInstantiated&);
...
};
객체 개수를 1개로 제한
- 객체를 생성자 함수 안에 그냥 넣는다.
class PrintJob;
class Printer
{
public:
void submitJob(const PrintJob& job);
void reset();
void performSelfTest();
...
frient Printer& thePrinter(); // friend 함수이므로 private 함수 접근 가능
prviate:
// 생성자가 private이므로 생성 불가
Printer();
Printer(const Printer& rhs);
...
};
Printer& thePrinter()
{
// 프린터 객체는 하나다.
// private 함수인 생성자에 접근 가능하므로 생성 성공
// static이므로 객체 개수가 1개임을 보장하게 된다.
static Printer p;
return p;
}
- 다른 구현 방법을 채택해도 된다.
- thePrinter() 함수를 클래스 내부 static 함수로 둔다.
-
class Printer { public: static Printer& thePrinter(); // 내부 정적 함수로 ... private: Printer(); Printer(const Printer& rhs); ... } // 대신 사용 시 코드가 조금 더 길어진다. Printer::thePrinter().reset();
-
- Printer와 thePrinter를 전역 유효범위에서 특정 네임스페이스로 옮긴다.
- 네임스페이스(namespace)는 어떤 타입의 이름 충돌을 막는 상위 개념의 범주이다.
- 클래스와 매우 흡사하지만, 제한자가 없다. 즉, public 뿐이다.
-
namespace PrintingStuff { class Printer { public: ... friend Printer& thePrinter(); private: Printer(); Printer(const Printer& rhs); ... }; Printer& thePrinter() { static Printer p; return p; } } // 앞에 네임스페이스를 붙여서 사용한다. PrintingStuff::thePrinter().reset(); // 하지만 using 선언으로 축약할 수 있다. using PrintingStuff::thePrinter; thePrinter().reset();
- thePrinter() 함수를 클래스 내부 static 함수로 둔다.
- 앞의 코드의 미묘한 특징
- Printer 객체가 클래스 정적 객체가 아니라 함수의 정적 객체로 선언되었다.
- 클래스에 넣으면 사용하든 하지 않든 생성되지만, 함수에 넣으면 사용할 때에서야 생성된다.
- 또한 클래스에 넣으면 언제 초기화 될지, 초기화 시점을 특정할 수 없다.
- 함수 안에 정의된 정적 객체와 인라인 사이의 관계
-
// 이렇게 짧고 간단한 함수인데 왜 인라인 함수로 지정하지 않았을까? Printer& thePrinter() { static Printer p; return p; }
- inline의 의미
- 함수가 호출된 부분 대신 그 함수의 몸체를 끼워넣고 즉시 처리해라.
- 허나, 비멤버 함수에서는 다른 의미를 가진다.
- 이 함수는 내부 연결(internal linkage)를 가진다는 뜻.
- (하지만, 1996년 7월에 인라인 함수 연결 형태를 외부 연결로 변경해서 아래 이슈는 더 이상 문제가 아니다.)
- 내부 연결을 가진 함수는 한 프로그램 안에서 중복될 수 있다.
- 즉, 내부 연결을 가진 함수의 코드가 프로그램의 목적 코드 안에 두 개 이상 나타날 수 있다.
- 결론적으로, 정적 객체가 여러 개 만들어질 수 있다는 뜻이다.
-
- Printer 객체가 클래스 정적 객체가 아니라 함수의 정적 객체로 선언되었다.
객체 개수를 n개로 제한
- 생성된 객체의 개수를 직접 세어서, 일정한 개수가 넘어갔을 때 예외를 일으키는 방법
class Printer
{
public:
class TooManyObjects(); // 너무 많은 객체가 요구될 때 사용할 예외 클래스
Printer();
~Printer();
...
private:
static size_t numObjects; // Printer 객체의 개수를 센다.
Printer(const Printer& rhs); // 복사는 금지
};
size_t Printer::numObjects = 0;
Printer::Printer()
{
if(numObjects >= n) // n개 이상이 되면 예외 발생
throw TooManyObjects();
++numObjects;
}
Printer~Printer()
{
--numObjects;
}
- 직관적이고 단순하다.
- 객체의 개수를 유동적으로 변경할 수 있다.
- 하지만 직관적이지 않은 생성법들에 의해서 문제가 발생할 수 있다.(상속 / 합성)
// 문제 상황 1 : 상속된 경우
class ColorPrinter : public Printer { ... };
Printer p;
ColorPrinter cp;
// 이 경우 Printer 객체는 2개가 소비된다.
// 1개로 제한되어 있는 경우 예외가 발생할 것이다.
// 문제 상황 2 : 합성된 경우
class CPFMachine
{
private:
Printer p;
FaxMachine f;
CopyMachine c;
...
};
CPFMachine m1;
CPFMachine m2;
// 역시 Printer 객체가 2개가 소비된다.
// 1개로 제한되어 있는 경우 역시나 예외가 발생한다.
객체 개수를 n개로 제한 2
- 생성자가 private로 선언된 클래스는 상속 / 합성에 불가능하다.
- 생성자를 대신할 함수를 만드는 방법.
class FSA
{
public:
// 사용하기 힘든 버전
static FSA* makeFSA();
static FSA* makeFSA(const FSA& rhs);
// 사용이 편한 버전
static unique_ptr<FSA> makeFSA();
static unique_ptr<FSA> makeFSA(const FSA& rhs);
...
private:
FSA();
FSA(const FSA& rhs);
...
}
// new를 호출한다?
// 받는 쪽에서 delete를 해줘야 한다는 문제가 있다.
FSA* FSA::makeFSA() {return new FSA()}
FSA* FSA::makeFSA(const FSA& rhs) {return new FSA(rhs); }
// 스마트 포인터를 리턴해 버리자.
unique_ptr<FSA> FSA::makeFSA() {return unique_ptr<FSA>(new FSA()); }
unique_ptr<FSA> FSA::makeFSA(const FSA& rhs) {return unique_ptr<FSA>(new FSA(rhs)); }
한 개로 제한하되, 런타임 중에 소멸도 시키고 싶다
- static 변수로 처리한 경우 아래 상황이 불가능하다.
Printer 객체를 생성;
객체 사용;
객체 소멸;
Printer 객체를 재생성;
객체 사용;
객체 소멸;
- 방금 전의 생성자를 대신할 함수와 카운팅 코드를 모두 합쳐버리면 된다.
class Printer
{
public:
class TooManyObjects{};
static unique_ptr<Printer> makePrinter();
~Printer();
...
private:
static size_t numObjects;
Printer();
Printer(const Printer& rhs);
};
size_t Printer::numObjects = 0;
Printer::Printer()
{
if(numObjects >= 1)
throw TooManyObjects();
++numObjects;
}
unique_ptr<Printer> Printer::makePrinter()
{
return unique_ptr<Printer>(new Printer);
}
- 1개 제한은, 간단히 일반화 가능하므로 범용적이다.
인스턴스 카운팅(Object-Counting) 기능을 가진 기본 클래스
- 위의 코드에서 인스턴스 카운팅 하는 코드들을 일반화 해볼 수 있을 것 같다.
- 인스턴스 카운팅을 상속받을 수 있도록 기본 클래스를 만든다?
- 하지만 이보다 좀 더 나은 방법이 있을 것 같다.
- 아래 템플릿 클래스의 인스턴스를 합성하여 사용한다.
template<typename BeingCounted>
class Counted
{
public:
class TooManyObjects {};
static size_t objectCount() { return numObjects; }
protected:
Counted() { init(); }
Counted(const Counted& rhs) { init(); }
~Counted() { --numObjects; }
private:
static size_t numObjects;
static const size_t maxObjects;
void init()
{
if(numObjects >= maxObjects) throw TooManyObjects();
++numObjects;
}
};
- 이를 private 상속 받거나, 멤버 변수로 포함시킨다.
// private 상속
class Printer : private Counted<Printer>
{
public:
...
using Counted<Printer>::objectCount; // 이 함수를 외부 사용자가 볼 수 있게 한다.
using Counted<Printer>::TooManyObjects; // 이 예외 클래스를 외부 사용자가 볼 수 있게 한다.
private:
...
};
- 마무리해야 할 부분이 남아있다.
- Counted 안에 선언되어 있는 정적 상수 멤버의 초기화
// numOjbects를 구현 파일에 정의하면, 자동으로 0으로 초기화횐다.
template<typename BeingCounted>
size_t Counted<BeingCounted>::numObjects;
// maxObjects 초기화는 사용자에게 맡긴다.
const size_t Counted<Printer>::maxObjects = 10;
728x90
'개발 > More Effective C++' 카테고리의 다른 글
[More Effective C++] 28. 스마트 포인터 (0) | 2024.09.09 |
---|---|
[More Effective C++] 27. 힙 전용, 힙 불가 클래스 만들기 (1) | 2024.09.06 |
[More Effective C++] 25. 함수를 가상 함수처럼 만들기 (4) | 2024.09.04 |
[More Effective C++] 24. 다형성의 비용 (0) | 2024.09.03 |
[More Effective C++] 23. 적절한 라이브러리 선택하기 (0) | 2024.09.02 |
Comments