Effective C++에서 사용되는 용어들에 대해 알아보자 (1) 💨

Effective C++ 책을 읽고 필요한 부분을 정리하고자 합니다.
부족한 점 궁금한점 댓글 남겨주시면 감사하겠습니다! 🙇

포스팅이 길어져 두 부분으로 나눴습니다.
(너무 길면 읽기 싫더라구요.. 하하.. 🙄)

목적

이 책의 목적은 c++를 효과적 으로 구사하는 방법에 대해 가르쳐 주는 책이다. 프로그래밍으로서 C++을 공부한 사람이라면, 이 책을 읽을 자격이있다. 해당 책은 구체적인 설명이 들어가 있지 않아 초심자가 보기에는 당황스러울 수 있다. 따라서, 초심자일 경우 C++ 기초를 먼저 보고 다시 이 책을 보기 바란다.

책의 내용은 크게 두 부류로 나뉜다.

설계 같은 경우 착수 시점에 결정을 잘 내리는 것이 중요한데, 해당 결정을 허접하게 내릴 경우 추후에 아주 고된 일을 겪게 된다. 그렇다고 설계 전략만 잘 짠다고 되는 건 아니다. 만약, C++의 언어적 특성을 모를 경우 불필요한 메모리 낭비 등이 생길 것이고 프로그램으로 HW가 망가지는 경우가 생길 수 있다.

이 책은 앞서 말한 것과 같이 현재의 설계보다 더 나은 설계를 뽑는 방법, 일상에서 자주 부딪히는 문제를 피하는 방법, 지금보다 효율을 더 높이 올리는 방법에 대한 안내서이지, “정석”과는 다른 느낌이다. 따라서, 내용도 “항목” 으로 구별되어져 있으며, 필요한 부분을 찾아서 보면된다.

용어 사용

선언 (declaration) 이란 ‘어떤 대상’의 이름과 타입을 컴파일러에게 알려주는 것이다. 구체적인 세부사항은 선언에 들어가지 않는다. 다음의 예시를 통해 확인해보자.

extern int x;  // 객체 선언

std::size_t numDigits(int number);  // 함수 선언

class Widget;  // 클래스 선언

template <typename T>  // 템플릿 선언
class GraphNode;

위의 x 를 “객체” 라고 정의했다. 보통 built-in 타입 같은 경우 “객체”라고 칭하지 않고 사용자 정의 타입에 대해서만 “객체” 라고 칭하는데 해당 책에서는 built-in 타입도 객체로 정의한다.

시그니처 (signature) 란 모든 함수 선언문에 들어가는 것을 말한다. 다시말해, 함수에 매개변수 리스트와 반환 타입을 시그니처 라고 한다. 보통 시그니처 는 매개변수 리스트만 말하는데, 해당책에서는 반환 타입도 포함해서 정의한다. 예를들어 위의 numDigits 같은 경우 std::size_t (int number)시그니처를 갖고 있다고 말한다.

정의 (definition) 란 선언에서 빠진 구체적인 세부사항을 컴파일러에게 제공하는 것을 의미한다. 객체의 경우에 있어 정의는 컴파일러가 그 객체에 대한 “메모리”를 마련해 놓은 부분을 말한다. 함수나 함수 템플릿에 대한 정의는 그들에 대한 “본문 (body)”을 제공하는 것을 말하고, 클래스 혹은 클래스 템플릿 같은 경우 “클래스 혹은 템플릿 멤버”를 넣어준 결과를 말한다.

아래의 예시를 통해 알아보자.

int x;  // 객체 정의 -> 메모리 할당

std::size_t numDigits(int number) {  // 함수 정의
  std::size_t digitsSoFar = 1;

  while ((number /= 10) != 0) ++digitsSoFar;
  return digitsSoFar;
}

class Widget() {  // 클래스 정의
public:
  Widget();
  ~Widget();
  ...
};

template<typename T>
class GraphNode {  // 템플릿 정의
public:
  GraphNode();
  ~GraphNode();
  ...
};

초기화 (initialization)란 어떤 객체에 최초의 값을 부여하는 과정이다. 사용자 정의 타입으로 생성한 객체의 경우, 초기화는 생성자에 의해 이루어 진다. 사용자 정의 타입은 기본 생성자 (default constructor) 를 갖고 있으며, 어떤 인자도 주어지지 않은 채로 호출될 수 있는 생성자를 말한다.

중요한 점은 기본생성자 같은 경우 원래부터 매개변수가 없거나 (컴파일러에 의해 만들어지거나) 모든 매개 변수가 기본 값을 갖고 있으면 생성 될 수 있다.

class A {
public:
  A();  // 기본 생성자
}

class B {
public:
  explicit B(int x = 0, bool b = true); // 기본 생성자
                                        // 매개 변수가 기본 값을 다 가지고 있음
}

class C {
public:
  explicit C(int x);  // 기본 생성자가 아님.
}

위의 코드는 생성자를 나타내는 코드이다. class Aclass B는 매개변수가 없거나 모든 매개변수가 기본 값을 갖기 때문에 기본 생성자를 가지고 있다고 할 수 있다. 하지만, class C 같은 경우 매개변수를 한개 가지고 있으며 기본 값을 갖고 있지 않기 때문에 인자가 1개 있는 생성자라고 볼 수 있으며 기본 생성자 는 아니다.

class를 객체화 할때는 기본 생성자가 필수로 있어야 하며, 없을 경우 컴파일러가 만들어준다. 하지만, class C 와 같이 인자가 한개 있는 생성자가 있을 경우 컴파일러가 암묵적으로 만들어주지 않기 때문에 사용자가 만들어야한다. 만약 만들지 않으면 기본 생성자 가 없다는 에러를 만나게 될 것이다!

위 코드에서, explicit 이라는 키워드를 볼 수 있는데 해당 키워드는 암시적 타입 형변환을 허용하지 않는다는 의미이다. explicit로 선언된 생성자는 explicit으로 생성되지 않는 생성자들과 비교할 때 꽤 쓸모가 있다. 프로그래머가 예상하지도 못했던 (바라지 않던..) 타입 변환을 막아주기 때문이다. 프로그래머가 코드를 짤 때 컴파일러의 타입 변환까지 고려하고 짜는 사람은 많지 않기 때문에, 다양한 에러를 마주할 수 있다. 해당 에러는 간단하지만 큰 코드에서는 모래에서 바늘 찾기 처럼 엄청난 시간이 든다. 따라서, explicit은 방파제 역할을 해준다고 생각하면 되고, 암시적 타입 변환에 생성자가 사용될 여지를 남겨둘 뚜렷한 이유가 없다면 생성자에는 explicit 사용을 적극 추천한다.

복사생성자 부터는 다음 포스팅을 참고하자.