일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 상속
- implicit conversion
- 영화 리뷰
- effective stl
- effective modern c++
- 참조자
- 예외
- iterator
- 언리얼
- 게임
- 반복자
- c++
- 영화
- std::async
- Smart Pointer
- resource management class
- 오블완
- UE4
- Effective c++
- lua
- reference
- 암시적 변환
- 보편 참조
- exception
- universal reference
- operator new
- virtual function
- more effective c++
- 티스토리챌린지
- 스마트 포인터
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 17. 특수 멤버 함수(special member function) 본문
Effective C++/Effective Modern C++
[Effective Modern C++] 17. 특수 멤버 함수(special member function)
김디트 2025. 2. 27. 11:33728x90
항목 17. 특수 멤버 함수들의 자동 작성 조건을 숙지하라
특수 멤버 함수(special member function)
- C++이 자동으로 작성하는 멤버 함수
- 꼭 필요한 경우에만 자동으로 작성된다.
- 인수를 받는 생성자만 명시적으로 선언된 클래스엔 기본 생성자가 작성되지 않는다.
- 작성된 특수 멤버 함수들은 암묵적으로 inline public nonvirtual이다.
- 가상 소멸자가 있는 기본 클래스를 상속받으면 소멸자는 가상으로 선언된다.
- C++98에서는...
- 기본 생성자, 소멸자
- 복사 생성자
- 복사 할당 연산자
- C++11에서는 두 가지가 추가되었다.
- 이동 생성자(move constructor)
- 이동 할당 연산자(move assignment operator)
class Widget
{
public:
...
Widget(Widget&& rhs); // 이동 생성자
Widget& operator=(Widget&& rhs); // 이동 할당 연산자
...
};
새로 추가된 함수들로 이동 연산 시
- 반드시 이동 연산이 일어난다는 보장은 없다.
- 사실 이동 '요청'에 더 가깝다.
- 이동을 적용할 수 없는 타입은 복사 연산으로 동작하게 된다.
- 결국 멤버별 이동은 이동할 원본 객체에 std::move를 적용하는 것에 불과하다.(항목 23 참조)
C++11 자동 작성 규칙
- 복사 연산과 마찬가지로, 명시적으로 선언한 이동 연산들은 자동으로 작성되지 않는다.
- 하지만, 복사 연산의 경우 각각(복사 생성자, 복사 할당 연산자)은 독립적으로 동작한다.
- 복사 생성자만 선언되어 있을 시,
- 복사 할당 연산자를 사용하면, 복사 할당 연산자는 컴파일러가 자동으로 작성한다.
- 즉, 컴파일 에러는 발생하지 않는다.
- 복사 생성자만 선언되어 있을 시,
- 이동 연산의 경우 독립적이지 않다.
- 이동 연산이 하나라도 커스텀 생성되어 있으면 이동 연산 관련은 자동 작성되지 않는다.
- 이동 생성자만 선언되어 있을 시,
- 이동 할당 연산자를 사용하면, 이동 할당 연산자를 컴파일러가 자동 작성하지 않는다.
- 즉, 컴파일 에러가 발생한다.
- 그 이유는?
- 커스텀 이동 연산을 만든 이유는 아마 기본 이동 로직이 그 클래스에 적합하지 않기 때문일 것이다.
- 그럼 자동으로 만든 함수가 의미가 없을 가능성이 크다.
- 복사 연산이 하나라도 커스텀 작성되어 있으면, 이동 연산들은 자동 작성되지 않는다.
- 그 이유는?
- 복사 연산을 선언했다는 것은, 일반 객체 복사 방식이 그 클래스에 적합하지 않기 때문일 것이다.
- 복사가 적합하지 않다면 이동 연산도 적합하지 않을 가능성이 크다.
- 그 이유는?
- 이동 연산이 하나라도 커스텀 작성되어 있으면, 복사 연산들은 자동 작성되지 않는다.
- 위와 마찬가지의 이유이다.
- C++98과의 호환성은?
- C++98에는 어차피 이동 연산이란 것이 없으므로 상충되지 않는다.
- 소멸자가 선언되어 있으면, 이동 연산들은 자동 작성되지 않는다.
- 복사 연산에 적용할 수는 없다. C++98과의 호환성 문제.
- 즉, 이동 연산들은 아래 세 조건이 모두 만족하고 꼭 필요한 경우, 자동으로 작성된다.
- 클래스에 그 어떤 복사 연산도 선언되어 있지 않다.
- 클래스에 그 어떤 이동 연산도 선언되어 있지 않다.
- 클래스에 소멸자가 선언되어 있지 않다.
- 특수 상황) 멤버 함수 템플릿이 있어도 이동 연산 자동 작성은 된다.
class Widget
{
...
template<typename T>
Widget(const T& rhs);
template<typename T>
Widget& operator=(const T& rhs);
...
};
// 만약 T가 Widget이면 기본 복사 생성자와 동일한 모양이다.
// 그러나, 그렇다고 하더라도 이동 연산들은 컴파일러가 자동 작성 해준다.
3의 법칙(Rule of Three)
- 복사 생성자, 복사 할당 연산자, 소멸자 중 하나라도 선언했으면 나머지도 모두 선언해야 한다는 규칙.
- 그 이유는?
- 복사 할당 연산을 커스텀한 이유는, 자원 관리를 직접 해야 한다는 뜻일 것이다.
- 복사 시, 소멸 시엔 관리하고 있는 메모리를 처리할 의무가 있다.
- C++98이 처음 제정될때는 위의 추론이 충분한 공감대를 얻지 못했기 때문에 독립적으로 자동 생성되게 되었다.
C++98의 복사 연산 코드를 C++11 제정에 따르도록 업그레이드 하기
- 복사 연산의 자동 생성에 의존하는 코드가 있다면 default 키워드를 사용하여 업그레이드 해주자.
class Widget
{
public:
...
~Widget(); // 커스텀 소멸자가 있다.
// 이동 연산과 같은 룰을 적용하면, 복사 연산은 자동 생성되면 안된다.
// 그럼에도 복사 생성이 필요하고, 기본 동작으로 동작해야 한다면...
...
// 컴파일러가 자동 생성하던 기본 복사 생성자를 명시적 선언
Widget(const Widget&) = default;
// 컴파일러가 자동 생성하던 기본 복사 할당 연산자를 명시적 선언
Widget& operator=(const Widget&) = default;
...
};
default의 추가 용도
- 다형성 클래스를 작성하며 소멸자를 virtual로 하고 싶다.
- 근데 소멸자 기능 자체는 기본의 것을 사용해야 한다면?
- 그리고 이렇게 소멸자를 직접 선언하면, 이동 연산의 자동 작성이 불가능해진다.
- 커스텀 소멸자를 두면서 기본 이동을 지원해야 한다면?
class Base
{
public:
// 가상 소멸자
virtual ~Base() = default;
// 이동 지원
Base(Base&&) = default;
Base& operator=(Base&&) = default;
// 복사 지원
Base(const Base&) = default;
Base& operator=(const Base&) = default
...
};
컴파일러가 자동생성 해줘도 기본으로 default 키워드 적용하기
- 이렇게 하는 게 의도가 더 명확하기도 할 뿐더러
- 미묘한 버그를 피할 수 있다.
// 아래 클래스는 복사 연산, 이동 연산, 소멸자가 없으므로 자동 생성될 것이다.
class StringTable
{
public:
StringTable() {}
...
private:
std::map<int, std::string> values;
};
// 근데 아래처럼 기본 생성과 소멸 기능을 추가했다면?
class StringTable
{
public:
StringTable()
{ makeLogEntry("Creating StringTable object"); } // 추가
~StringTable()
{ makeLogEntry("Destroying StringTable object"); } // 추가
...
private:
std::map<int, std::string> values;
};
// 이제 이동 연산은 자동 작성되지 않는다.
// 허나, 복사 연산은 자동 작성된다.
// 이동 연산으로 처리되던 것들이 이젠 복사 연산으로 처리되게 된다!!
// 컴파일 에러조차 없다..
728x90
'Effective C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 19. std::shared_ptr (0) | 2025.03.04 |
---|---|
[Effective Modern C++] 18. std::unique_ptr (0) | 2025.02.28 |
[Effective Modern C++] 16. thread safety한 const 멤버 함수 (0) | 2025.02.26 |
[Effective Modern C++] 15. constexpr (0) | 2025.02.25 |
[Effective Modern C++] 14. noexcept (0) | 2025.02.24 |
Comments