Effective C++

Effective C++

2023년 7월 13일

용어 #

  • 선언(declaration) : 이름과 타입을 컴파일러에게 알려주는것
  • 시그니쳐(signature) : 함수의 매개변수 리스트와 반환타입을 의미하며 함수의 선언에 사용된다. 공식C++에서는 반환타입은 시그니쳐에 포함되지 않는다.
  • 정의(definition) : 선언에서 알수없는 구체적인 세부사항을 컴파일러에게 전달하는것
  • 기본생성자(default constructor) : 명시적인 인자 없이 호출이 가능한 생성자. 모든 인자에 초기값을 지정해도 기본생성자이다.
  • explicit 생성자 : 묵시적인 호출을 허용하지 않겠다는 의미이다. A a(10) 이런식으로 직접 호출해야한다.
  • 미정의동작(undefined behavior) : 코드가 어떻게 동작될지 정의되지 않은 코드들이 있다.
  • 사용자(client) : 코드를 사용하는 모든 사람을 의미.

이름짓기 규약 #

  • lhs, rhs : 좌변 우변
  • 포인터 : 포인터는 타입앞에 p를 붙인다. GameCharacter *pgc; 참조도 비슷한 방식으로 r을 사용한다.
  • 매크로 : 전체를 대문자로 표시 #define VALUE 10
  • 상수 : 파스칼케이스로 표시 const int Value = 10

Effective C++ #

item1. C++을 언어들의 연합체로 바라봐라 #

C++은 C에 객체지향, 함수형, 제너릭, 메타프로그래밍 등의 패러다임을 추가하고 계속 발전해왔다.
일반 C는 값전달이 참조보다 효율적이지만, 객체지향에선 참조가 더 효율적이듯 패러다임마다 효율적으로 프로그래밍 하는 방법이 다르다.

item2. #define 매크로보다 const, enum, inline을 떠올리자 #

매크로 상수 #

#define VALUE 10 문법은 전처리기 시점에 VALUE를 전부 10으로 바꿔주기 때문에 컴파일과정에서 컴파일 에러가 발생하면 찾기 어려울 수 있고, 심볼테이블에 저장되지 않기 때문에 디버깅도 어려워진다.

상수포인터는 헤더에 넣고 다른 파일이 인클루드해서 쓰는데, 포인터와 가리키는 대상 모두 const화 해야한다. const Knight * const TmpKnight
클래스 멤버로 상수를 정의하면, 상수의 범위도 제한할 수 있고 캡슐화(private)도 가능하다. 멤버상수는 선언때 초기화하지만 옛날 컴파일러는 정의때 초기화해야할수도 있다.

enum은 define과 동일하게 메모리를 할당하지 않지만, 컴파일 타임에 치환되며 심볼테이블에 저장된다.

enum hack #

혹시나 옛날 컴파일러에서 선언때 상수화할 값이 필요한 경우 ex) 배열 사이즈 지정

1class GamePlayer {
2private:
3    enum { Num = 5; }
4    int scores[Num];
5};

매크로 함수 #

매크로함수는 전처리 시점에 계산되는게 장점이다 #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) 코드가 있다고 했을때 a에 ++a 를넘긴다면, a가 큰경우 ++a가 두번 호출될 수 있다. inline 함수로 대체 가능하다.

item3. 낌새가 보이면 const를 갈겨라 #

일반변수와 포인터 #

위치에따라 변수자체의 상수화, 변수가 가리키는 값의 상수화로 쓸 수 있다.

const iterator #

iterator에 const를 붙이면 T* const it가 돼서 iterator 자체의 값을 변경할 수 없게된다. iterator가 가리키는 값을 변경하지 못하게 하려면 const T* cit 의미를 갖는 const_iterator를 사용해야한다.

함수의 const 반환값 #

const Number operator*(const Number& lhs, const Number& rhs) const Number로 리턴하는 이유는 a * b = c 처럼 함수가 반환된 임시객체에 대입하는것을 금지시키기 위해서이다.

const 멤버함수 #

해당 메서드 안에서는 객체의 멤버를 변경하지 않는다(비트수준 상수)는 뜻으로, 상수객체가 호출할 수 있는 메서드가된다.

멤버변수에 mutable 키워드를 붙이면 비트수준 상수성을 무시하고 const함수 내부에서 값을 변경할 수 있다.

같은 매개변수를 가지고있어도 const 여부로 각각 오버로드가 가능하다
코드 중복을 피하려면 비상수 버전의 메서드에서 상수버전의 메서드를 호출하면 된다. 논리적으로 상수버전은 변함이 없다는뜻이고, 비상수버전은 변함이 있어도되기 때문인것도 있지만, 그렇게되면 비상수 함수를 호출해야하는데 상수 -> 비상수는 const_cast로만 변환이 가능하기 때문에 변환이 자유롭지 못하다.

1const char& operator[] (std::size_t pos) const
2{ return text[pos]; }
3
4char& operator[] (std::size_t pos) 
5{
6    return const_cast<char>(                        // 리턴값에서 const를 뗌
7        static_cast<const TextBlock&>(*this)[pos]   // this에 const를 붙여 const버전 [] 호출
8    );
9}

item4. 객체를 사용하기 전에 꼭 초기화해라 #

대입과 초기화는 다르다. 대입은 바로 복사생성자를 호출하면 될것을 생성자->대입연산 이렇게 두번 일을하게 만든다. 생성자의 멤버 초기화리스트에서 초기화하자.

부모클래스가 먼저 초기화되고, 멤버변수는 선언된 순서대로 초기화된다.

파일 전역객체, namespace 전역객체, 파일 static 객체, 클래스 static 객체, 함수 static 객체는 정적객체라고 한다.
지역성을 갖는 함수 static 객체를 제외하고는 번역단위(오브젝트파일 단위. include 까지 포함됨)로 초기화가 진행되는데, 파일의 순서는 보장받지 못하기 때문에 다른 번역단위의 정적객체는 extern 으로 선언 이후 직접 초기화에 사용하면 안된다.

  • extern : 링커가 해당 심볼을 찾아서 링킹할때 다른 오브젝트파일에서 가져옴

싱글톤 패턴으로 구현해서 전역함수 안에 static 객체를 넣는 방식으로 지역성을 갖게 구현하고 선언을 include 하면 호출하면 첫 호출때 static 지역변수로 생성된다.
싱글톤은 비지역 정적객체를 지역 정적객체로 구현하는 디자인패턴이다.

item5. #

comments powered by Disqus