[Effective C++ 정리 #0] C++를 진짜 잘 쓰기 위한 전제 조건들
Effective C++를 시작하며
『Effective C++』는 단순히 C++ 문법을 배우는 책이 아니라고 합니다. 이 책은 C++라는 복잡한 언어를, 어떻게 하면 더 효과적으로 설계하고 구현할 수 있을지 초점을 맞추고 있습니다. C++를 알긴 알지만, 실제로 더 읽기 쉬운, 유지 보수하기 쉬운, 효율적인 코드를 작성하고 싶은 사람들을 위한 가이드입니다.
이 책은 총 55개의 조언(Items)으로 구성되어 있으며, 각 Item은 독립적으로 읽을 수 있으면서도 다른 Item들과 유기적으로 연결되어 있습니다. 앞으로 각 Item들을 공부하고 그 기록을 남겨볼까 합니다. 참고로. 저는 컴퓨터공학을 전공하지 않았으며, 프로그래밍을 공부하는 과정에서의 이해와 생각을 정리하기 위해 글을 작성하고 있습니다. 따라서 내용 중 일부에 오류나 부정확한 설명이 있을 수 있으며, 피드백은 언제든지 환영합니다. 확인 후 수정하도록 하겠습니다. 전문적인 해설이 아닌 개인적 시선에서의 정리임을 참고하고 읽어주시면 감사하겠습니다.
이 책은 무엇을 다루는가?
책에서 제공하는 조언은 크게 두 가지 범주로 나뉩니다.
- 첫째, 설계 전략: 상속 vs. 템플릿, 멤버 vs. 비멤버 함수, 합성 vs. 상속 등 어떤 구조가 더 적절한지 선택하는 기준을 제시합니다.
- 둘째, 언어 기능의 세부 사용법: 복사 생성자, 소멸자, 연산자 오버로딩, const의 활용 등 구체적인 문법 사용에서 실수하지 않는 법을 다룹니다.
이러한 결정들은 초기에는 중요하지 않아 보이지만, 시간이 흐르고 프로젝트가 커질수록 치명적인 기술 부채가 될 수 있습니다. 책은 이러한 문제들을 피할 수 있는 선제적인 기준을 제공합니다.
왜 단순한 문법 지식만으로는 부족한가?
C++는 강력한 언어입니다. 다양한 스타일의 프로그래밍을 지원하고, 저수준 제어부터 고수준 추상화까지 표현할 수 있는 폭이 넓습니다. 하지만 그만큼 제대로 사용하지 않으면 오류 가능성이 높고, 코드가 쉽게 복잡해질 수 있습니다.
잘 작성된 C++ 코드는 읽기 쉽고, 효율적이며, 재사용이 쉽습니다. 하지만 **작은 실수 하나가 프로그램의 전체 구조에 영향을 줄 수 있습니다.** 그래서 이 책에서는 “단순히 작동하는 코드”가 아니라, “**오래가고 안전한 코드**”를 어떻게 작성할지를 알려주려 합니다.
중요한 용어에 대한 전제
책에서는 몇 가지 핵심 용어를 명확히 정의합니다.
Declaration (선언)
컴파일러에게 이름과 타입만 알려주는 것. 메모리 할당이나 구체적인 구현은 없음.
extern int x; // 객체 선언
std::size_t numDigits(int number); // 함수 선언
class Widget; // 클래스 선언
template<typename T> class GraphNode; // 템플릿 클래스 선언
Definition (정의)
선언에서 빠졌던 구현을 포함하여, 실제 동작이나 메모리를 정의하는 부분.
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 C는 기본 생성자가 아닙니다.
class A {
public:
A(); // 기본 생성자
};
class B {
public:
explicit B(int x = 0, bool b = true); // 기본 생성자 (explicit)
};
class C {
public:
explicit C(int x); // 기본 생성자 아님
};
explicit 생성자는 암묵적인 타입 변환을 방지합니다. 아래 예시처럼 의도되지 않은 변환을 막는 데 유용합니다.
void doSomething(B bObject);
B bObj1;
doSomething(bObj1); // OK
doSomething(28); // Error (암시적 변환 불가)
doSomething(B(28)); // OK (명시적 변환)
복사 생성자와 대입 연산자
복사 생성자는 객체를 값으로 전달하거나 초기화할 때 사용되며, 복사 대입 연산자는 기존 객체에 값을 복사할 때 사용됩니다.
class Widget {
public:
Widget(); // 기본 생성자
Widget(const Widget& rhs); // 복사 생성자
Widget& operator=(const Widget& rhs); // 복사 대입 연산자
...
};
Widget w1; // 기본 생성자
Widget w2(w1); // 복사 생성자
w1 = w2; // 복사 대입 연산자
Widget w3 = w2; // 복사 생성자 (주의! = 는 대입이 아니라 초기화)
아래와 같이 함수 인자에 객체를 값으로 넘길 경우에도 복사 생성자가 호출됩니다.
bool hasAcceptableQuality(Widget w); // w는 값 전달됨
Widget aWidget;
if (hasAcceptableQuality(aWidget)) ...
STL과 함수 객체의 개념
책에서는 STL(Standard Template Library)을 자주 사용합니다. STL은 컨테이너, 반복자, 알고리즘, 함수 객체 등으로 구성되어 있습니다. 함수 객체는 operator()
를 오버로드한 객체로, 함수처럼 행동할 수 있습니다.
책의 예제와 설명에서는 STL이 자주 등장하므로, STL을 모른다면 별도의 참고 자료가 필요할 수 있습니다. 하지만 익숙해지면 효율적인 코드 작성과 추상화에 큰 도움이 됩니다.
정의되지 않은 동작 (Undefined Behavior)
C++의 핵심 이슈 중 하나는 **정의되지 않은 동작**입니다. 이는 컴파일러가 보장하지 않으며, 프로그램이 어떻게 동작할지 예측할 수 없습니다.
예를 들어 다음과 같은 코드는 모두 undefined behavior입니다:
int* p = nullptr;
std::cout << *p; // null 포인터 역참조 → 정의되지 않은 동작
char name[] = "Darla";
char c = name[10]; // 범위를 벗어난 배열 접근 → 정의되지 않은 동작
책에서는 이런 위험 요소들을 피하기 위한 전략도 함께 제시합니다.
가이드라인이지만 정답은 없음!
책에서 제시하는 55개의 Item은 규칙이 아닙니다. 좋은 습관을 형성하고, 더 나은 선택을 위한 기준입니다. 항상 예외는 있고, 모든 프로젝트에 무조건 적용할 수 있는 절대적인 원칙은 아닙니다.
따라서 각 Item에 함께 제시되는 설명의 맥락(context)이 매우 중요합니다. 왜 이런 결론이 나왔는지 이해해야만, 해당 조언을 내 코드에 적용할 수 있는지 판단할 수 있습니다.
정리하며
- 이 책은 C++ 문법서가 아니라, 실전에서 ‘잘 사용하는 법’을 다룬 책입니다.
- 설계 전략부터 언어의 세부 문법까지, 문제를 피하고 구조적으로 탄탄한 코드를 위한 조언이 담겨 있습니다.
감사합니다.