[ Effective C++ ] 항목 45 : "호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방!

Minsu._.Lighting·2023년 12월 14일
0

[ Effective C++ ] 정리 모음집
" C++ 프로그래머의 필독서, 스콧 마이어스의 Effective C++ 를 읽고 내용 요약 / 정리 "

[핵심]

" 제목이 곧 핵심! "

  • 호환되는 모든 타입을 받아들이는 멤버 함수를 만들려면 멤버 함수 템플릿을 사용하자!
  • 일반화된 복사 생성 연산과 일반화된 대입 연산을 위해 멤버 템플릿을 선언했다 하더라도, 보통의 복사 생성자와 복사 대입 연산자는 여전히 직접 선언해야 한다!

💡 스마트 포인터로 대신할 수 없는 포인터의 특징

📌 포인터의 암시적 변환

calss Top { ... };
class Middle : public Top { ... };
class Bottom : public Middle { ... };

Top* pt1 = new Middle;			// Middle* >> Top*의 암시적 변환
Top* pt2 = ndw Bottom;			// Bottom* >> Top*의 암시적 변환
const Top* pct2 = pt1;			// Top* >> const Top*의 암시적 변환
  • 파생 클래스 포인터는 암시적으로 기본 클래스 포인터로 변환된다

  • 비상수 객체에 대한 포인터는 상수 객체에 대한 포인터로 임시적 변환이 가능하다



💡 사용자 정의 스마트 포인터로 포인터의 암시적 변환을 흉내 내보자!

template<typename T>
class SmartPtr
{
public:
	explicit SmartPtr(T* realPtr);
    ...
};

SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);

SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);

SmartPtr<const Top> pct2 = pt1;
  • 변환이 이루어지지 않는다
    - 같은 템플릿으로부터 만들어진 다른 인스턴스들 사이에는 어떤 관계도 없기 때문에, SmartPtr<Middle>SmartPtr<Top> 은 완전히 별개의 클래스
    - 원하는 변환을 위해서는 직접 구현해야 한다

📌 스마트 포인터 생성자를 직접 만들어 보자

  • 불가능한 방법이다!
    - 클래스 계통이 확장된다던지 하면 모든 생성자를 만들어 내야 한다...
class BelowBottom : public Bottom { ... };


💡 사용자 정의 스마트 포인터로 포인터의 암시적 변환을 흉내 내보자!

template<typename T>
class SmartPtr
{
public:
	template<typename U>
    SmartPtr(const SmartPtr<U>& otehr);
    ...
};
  • 템플릿을 인스턴스화 해 '무제한' 개수의 함수를 만들어 냄

  • 위 생성자를 일반화 복사 생성자라고 부른다
    - 같은 템플릿을 써서 인스턴스화 되지만 타입이 다른 타입의 객체로 부터 원하는 객체를 만들어 주는 생성자

  • explicit 키워드를 사용하지 않음으로써 암시적 변환 허용
    📢 하지만 이 행동으로 인해 원하지 않는 동작도 수행한다..
    (SmartPtr<Top>에서 SmartPtr<Bottom> 변환, SmartPtr<int>에서 SmartPtr<double> 변환 등)


📌 스마트 포인터 생성자를 직접 만들어 보자

template<typename T>
class SmartPtr
{
public:
	template<typename U>
    SmartPtr(const SmartPtr<U>& otehr)
    : heldPtr(other.get()) { ... }
    
    T* get() const { return heldPtr; }
    ...
private:
	T* heldPtr;
};

📌 스마트 포인터 생성자를 직접 만들어 보자

template<class T> class shared_ptr
{
public:
	template<class Y>
    	explicit shared_ptr(Y* p);
	template<class Y>
    	shared_ptr(shared_ptr<Y> const& r);    
	template<class Y>
    	explicit shared_ptr(weak_ptr<Y> const& r);        
 	template<class Y>
    	explicit shared_ptr(auto_ptr<Y>& r);
	template<class Y>
    	shared_ptr& operator=(shared_ptr<Y> const& r);
	template<class Y>
    	shared_ptr& operator=(auto_ptr<Y>& r);        
    ...
};
  • 일반화 복사 생서자 외 모든 생성자는 explicit 로 선언
    - shared_ptr로 만든 어떤 타입으로부터 또 다른(shared_ptr로 만든) 타입으로 진행되는 암시적 변환은 허용되지만 기본제공 혹은 다른 스마트 포인터 타입으로부터 변환되는 것은 막겠다는 뜻, 명시적 변환은 허용 한다.

  • 생성자와 대입 연산자에 넘겨지는 auto_ptr은 const로 선언하지 않음
    - 복사 연산으로 객체가 수정되면 소유권이 복사된 객체 하나만 가져야 하는 auto_ptr의 특성을 반영
    - weak_ptr은 cosnt로 선언 함

📢 shared_ptr 객체가 자신과 동일한 타입의 다른 shared_ptr 객체로부터 생성된다면 컴파일러는 복사 생성자를 만들까? 아니면 일반화 복사 생성자 템플릿을 인스턴스화 할까?

  • 복사 생성자를 만든다...
    • 템플릿은 "컴파일러는 프로그래머가 직접 선언하지 않으면 자동으로 함수(생성자, 복사생성자 등) 를 만든다." 라는 언어의 규칙을 바꾸지 않는다.

    • 일반화 복사 생성자의 선언은 자동으로 만들어지는 복사 생성자를 막는 행위가 아니다.


      📢 따라서 보통의 복사 생성자 까지 직접 구현해 줘야 한다!
template<class T> class shared_ptr
{
public:
	shared_ptr(shared_ptr const& r);
    
    template<calss Y>
    	shared_ptr(shared_ptr<Y> const& r);
        
    shared_ptr& operator=(shared_ptr const& r);
        
    template<calss Y>
    	shared_ptr& operator=(shared_ptr<Y> const& r);
    ...
};
profile
오코완~😤😤

0개의 댓글

관련 채용 정보