5 분 소요

모던 C++

개요

클래스는 단일 책임 원칙(Single Responsibility Principle)에 따라 단 하나의 책임만 갖도록 설계하고, 단단한 코딩 계약에 의해 잘못 사용하기엔 어렵게, 바르게 사용하기엔 쉽게 구현해야 합니다.(캡슐화 참고)

암시적 정의 차단

컴파일러가 은근슬쩍 암시적으로 정의한 것들은 의도한대로 동작하지 않을 수도 있고, 현재는 의도한 대로 동작하지만, 누군가가 기능 추가나 리팩토링하면서 바뀔 수 있습니다.

따라서, 현재 사용하지 않는다면, 모두 차단하십시오.

항목 내용
암시적 기본 생성자 접근 지정자 private또는 protected 사용
또는 다른 생성자 정의
암시적 복사 생성자 접근 지정자 private또는 protected 사용
암시적 복사 대입 연산자 접근 지정자 private또는 protected 사용
암시적 소멸자 암시적 소멸자와 호환되도록 멤버 변수 정의(스마트 포인터(auto_ptr, unique_ptr, shared_ptr 등)나 Holder를 사용)
형변환 생성자 explicit로 정의하여 암시적 형변환 차단

(C++11~) 우측값 참조(&&)와 이동 생성자와 이동 대입 연산자가 추가되어 이동 연산을 지원하며, 임시 개체 대입시 속도가 향상되었습니다. 이동 생성자이동 대입 연산자는 암시적으로 생성되므로 사용하지 않는다면 차단해야 합니다.
(C++11~) default, delete가 추가되어 암시적으로 생성되는 멤버 함수의 사용 여부를 좀더 명시적으로 정의할 수 있습니다. private보다는 delete를 사용하는 편이 더 낫습니다.

최소 public

가시성이 최소화되어 접근할 수 있는 항목이 적을 수록 사용하기 쉽습니다.

항목 내용
멤버 변수 모두 private
멤버 함수 외부에서 사용하는 것만 public
상속 부모 개체의 public을 외부로 노출해야 하는 경우만 public 상속
PImpl 이디엄 구현의 상세 정보를 은닉

완전한 생성자

개체가 생성될때 완전한 상태로 생성합니다.

완전하지 않은 생성자는 다음과 같은 문제가 있습니다.

  1. 별도로 Setter 함수를 호출해야 하므로 사용하기 번거롭습니다.(초기화 리스트 참고)
  2. 기본값으로 대충 생성후 값을 세팅하면 불필요한 복사 대입 연산 오버헤드가 발생합니다.(초기화 리스트 참고)
  3. 개체 사용자는 개체 내부 구조를 완전히 파악해야만 제대로 사용할 수 있습니다. 코딩 계약 을 투명하게 하여 잘못 사용하기엔 어렵게, 바르게 사용하기엔 쉽게 구현해야 합니다.(캡슐화 참고)
  4. 불완전하게 생성된 개체를 사용했을때 발생하는 버그는 예측하기 힘듭니다.
  5. 불완전하게 생성된 개체에 별도 Setter 함수를 호출하여 완전하게 만드는 중에 예외가 발생하면, 이미 생성된 예외 보증 처리를 위해 소멸시켜야 합니다. 혹시나 이미 이 개체를 참조하는 곳이 있다면, 처리가 곤란합니다.(초기화 리스트 참고)

따라서,

  1. 개체 생성에 필요한 모든 인자생성자에서 나열하고,
  2. 생성중 오류가 발생하면 예외를 발생시켜 그동안 만들어 둔건 소멸시켜 버려야 합니다.
항목 내용
인자 나열 생성할 때 필요한 모든 인자를 나열
초기화 리스트 초기화 리스트멤버 변수 초기화
시스템 종속성이 높은 개체 생성자private로 만들고 별도의 Create()함수 제공(생성자 접근 차단 - private 생성자 참고)
멤버 변수 스마트 포인터(auto_ptr, unique_ptr, shared_ptr)나 Holder와 같은 스택 개체를 사용하여, 생성자등에서 예외가 발생했을때 자동 소멸되도록 함

완전한 함수

함수 구현은 다음 사항을 준수하여야 합니다.

  1. 생성자와 마찬가지로 필요한 인자를 모두 전달하여야 합니다. 코딩 계약 을 투명하게 하여 잘못 사용하기엔 어렵게, 바르게 사용하기엔 쉽게 구현해야 합니다.(캡슐화 참고)
  2. 인자 전달이나 함수 리턴값 전달시 복사 부하를 최소화 해야 합니다.
  3. 상수성 계약을 위반하지 않도록 작성해야 합니다.
  4. 함수내에서 예외가 발생하면 그동안 만들어 둔건 소멸시켜 버려야 합니다.
항목 내용
인자 나열 함수에 필요한 모든 인자를 나열
인자 복사 부하 기본 자료형인 경우 값 복사로, 클래스 타입인 경우 참조자로 작성(Getter 함수 참고)
리턴값 복사 부하 리턴값 최적화가 가능하도록 값 타입으로 리턴
* 기본 자료형인 경우 값 타입
* 생성한 개체인 경우 값 타입(값 타입 리턴값 참고)
* 클래스 멤버 변수인 경우 참조자(Getter 함수 참고)
상수성 멤버 변수를 수정하지 않는다면, 상수 멤버 함수로 작성하고, 상수 참조를 리턴(Getter 함수 참고)
논리적 상수성인 것은 최대한 예외가 발생하지 않도록 구현
함수내 지역 변수 스택 개체를 사용하여 획득된 자원은 유효 범위가 벗어났을때 자동 소멸되도록 함(RAII와 Holder 와 Restorer - 자원 획득과 안전한 소멸(복원))

소유권 분쟁 차단

포인터 멤버 변수 사용시에는 소유권 분쟁이 발생합니다.

소유권 분쟁이 발생하지 않도록 스마트 포인터(auto_ptr, unique_ptr, shared_ptr)를 사용하여,

  1. 소유권 이전을 하거나(auto_ptr, unique_ptr),
  2. 깊은 복제를 하거나,
  3. 자원을 공유하거나(shared_ptr),
  4. 유일한 자원으로 대체해서 사용

하여야 합니다.

항목 내용
복사 생성자 복사 생성시 소유권을 처리할 수 있는 스마트 포인터 사용(복사 생성자만 지원하는 스마트 포인터참고)
복사 대입 연산자 복사 대입 연산시 소유권을 처리할 수 있는 스마트 포인터 사용(복사 대입 연산자까지 지원하는 스마트 포인터 참고)

자원 획득은 초기화

획득된 자원은 반드시 소멸되어야 합니다.(RAII(Resource Acquisition Is Initialization) 참고) 소멸자에서 명시적으로 delete 하거나 스마트 포인터(auto_ptr, unique_ptr, shared_ptr)를 이용할 수 있습니다.

항목 내용
소멸자 멤버 변수에 스마트 포인터(auto_ptr, unique_ptr, shared_ptr)를 사용하여, 암시적 소멸자와 호환되어 자동 소멸되도록 하거나, 명시적으로 delete 호출
멤버 변수 스마트 포인터(auto_ptr, unique_ptr, shared_ptr)나 Holder를 사용하여 암시적 소멸자에서 자동 소멸되도록 함(포인터 멤버 변수 참고)

예외 안전에 좋은 클래스 설계

예외가 발생하면, 예외가 발생하기 전의 상태로 돌아가야 합니다. 개체의 일부 내용만 복원하는게 아니라, 완전히 예외 발생 전의 상태로 되돌아 가야 합니다. (예외 보증 참고)

따라서, 예외에 대해 강한 보증을 해주어야 하며, 다음을 준수하시기 바랍니다.

  1. 완전한 생성자로 생성하여 생성중 오류가 발생하면 예외를 발생시켜 그동안 만들어 둔건 소멸시켜 버리고,
  2. 복사 대입 연산중 예외가 발생하지 않도록 nothrow swap을 이용해서 복사 대입 연산을 구현하고,
  3. 함수 실행중 예외가 발생하면, 스택 풀기 메카니즘에 맞게 이전 상태로 되돌리고,
  4. 스택 풀기가 꼬이지 않게 소멸자에서는 예외가 발생하지 않도록 구현해야 합니다.
항목 내용
생성자 완전한 생성자로 구현하고, 생성중 오류가 발생하면 예외를 발생시킴.
멤버 변수 스마트 포인터(auto_ptr, unique_ptr, shared_ptr)나 Holder와 같은 스택 개체를 사용하여, 생성자등에서 예외가 발생했을때 자동 소멸되도록 함
복사 대입 연산자 멤버 변수가 1개인 경우 스마트 포인터(shared_ptr)를 사용하고, 2개 이상이면 swap을 이용한 예외 보증 복사 대입 연산자를 사용
nothrow swap swap() 구현시 복사 부하나 예외 발생이 없도록, 포인터 멤버 변수로 구현(nothrow swapPImpl 이디엄 참고)
함수내 지역 변수 스택 개체를 사용하여 획득된 자원은 유효 범위를 벗어났을때 자동 소멸되도록 함(RAII와 Holder 와 Restorer - 자원 획득과 안전한 소멸(복원))
소멸자 기본적으로 소멸자에서 예외 발생없이 정리하되, 예외 방출의 소지가 있으면 Release()와 같은 별도의 정리 함수 제공(소멸자에서 예외 방출 금지 참고)

형변환 차단

암시적인 형변환은 제공하지 않습니다. 필요하다면 명시적으로 형변환 함수를 제공합니다.

항목 내용
형변환 연산자 정의 정의하지 않음
형변환 생성자 explicit로 정의
명시적 형변환 함수 형변환이 꼭 필요하다면, 명시적으로 구현

(C++11~) explicit 형변환 연산자가 추가되어 명시적으로 형변환 할 수 있습니다.

부모 개체의 생성자와 복사 대입 연산자

상속 특성에 맞게 protectedprivate으로 제공합니다.

항목 내용
기본 생성자, 값 생성자 상속 강제가 필요하므로 protected 생성자를 사용합니다.
복사 생성자 인터페이스라면 사용못하게 private로 막고, 추상 클래스라면 상황에 따라 protectedprivate를 사용하고, 필요하면 Clone()가상 복사 생성자를 구현합니다.(가상 복사 생성자 참고)
복사 대입 연산자 사용하지 못하게 private로 막습니다.(부모 개체의 복사 대입 연산자 참고)

부모 개체의 소멸자

상속 특성에 맞게 적합한 소멸자를 사용합니다.

항목 내용
부모 개체로 사용하지 않는 개체 상속하지 않음
public Non-Virtual 소멸자 사용(상속 제한 참고)
is-a 관계 다형 소멸을 하려면 public Virtual 소멸자 사용
has-a 관계 protected Non-Virtual 소멸자 사용
추상 클래스 다형 소멸을 하려면 public Virtual 소멸자 사용
다형 소멸이 필요 없으면 protected Non-Virtual 소멸자 사용
인터페이스 protected Non-Virtual 소멸자 사용

(C++11~) final이 추가되어 가상 함수를 더이상 오버라이딩 못하게 할 수 있고, 강제적으로 상속을 제한할 수 있습니다.

댓글남기기