Effective C++
Updated:
용어 사용에 대하여
선언(declaration)은 코드에 사용되는 ‘어떤 대상’의 이름과 타입을 컴파일러에게 알려 주는 것이다. 하지만 구체적인 세부사항은 선언에 들어 있지 않다.
- 객체 선언
- extern int A;
- 함수 선언
- std::size_t A(int B);
- 클래스 선언
- class A;
- 템플릿 선언
- template
class GraphNode;
- template
모든 함수의 선언문에는 시그니처(signature), 다시 말해 그 함수의 매개변수 리스트와 반환타입이 나와있다. 함수의 경우엔 시그니처가 그 함수의 타입이다. 앞에서 본 함수 A 함수의 시그니처는 std::size_t A(int) 이다. 다시 말하면 ‘int 하나를 취하고, std::size_t를 반환하는 함수’라는 의미다.
정의(definition)는 선언에서 빠진 구체적인 세부사항을 컴파일러에게 제공하는 것이다. 객체의 경우에 있어서 정의는 컴파일러가 그 객체에 대한 메모리를 마련해 놓는 부분이 된다. 함수나 함수 템플릿에 대한 정의는 그들에 대한 코드 본문(body)을 제공하는 것이다. 클래스 혹은 클래스 템플릿의 경우에는 그 클래스 혹은 템플릿의 멤버를 넣어준 결과가 정의이다.
초기화(initialization)는 어떤 객체에 최초의 값을 부여하는 과정이다. 사용자 정의 타입으로 생성한 객체의 경우, 초기화는 생성자에 의해 이루어진다. 기본 생성자(default constructor)는 어떤 인자도 주어지지 않은 채로 호출될 수 있는 생성자이다. 원래부터 매개변수가 없거나 모든 매개변수가 기본 값을 갖고 있으면 기본 생성자가 될 수 있다.
explict로 선언된 생성자는 프로그래머가 예상하지 못했던(바라지 않는) 타입 변환을 막아준다. 암시적 타입 변환에 생성자가 사용될 여지를 남겨둘 뚜렷한 이유가 없는 한, 생성자는 explicit 선언을 우선적으로 하자.
복사 생성자(copy constructor)
복사 생성자(copy constructor)는 어떤 객체의 초기화를 위해 그와 같은 타입의 객체로부터 초기화할 때 호출되는 함수이고, 복사 대입 연산자(copy assignment operator)는 같은 타입의 다른 객체에 어떤 객체의 값을 복사하는 용도로 쓰이는 함수이다.
class A
{
public:
A(); // 기본 생성자
A(const A& B); // 복사 생성자
A& operator=(const A& B); // 복사 대입 연산자
};
A a1; // 기본 생성자 호출
A a2(a1); // 복사 생성자 호출
a1 = a2; // 복사 대입 연산자 호출
A a3 = a2; // 복사 생성자 호출
복사 생성자는 그 중요도에 있어서 꽤나 각별한 함수이다. 값에 의한 객체 전달을 정의해주는 함수가 바로 복사 생성자이기 때문이다. 다음 예를 보자
bool testFunction(A rA);
...
A tA;
if (testFunction(tA)) ...
해당 코드에서 매개변수 tA는 testFunction 함수에 값으로 넘겨지도록 되어 있으므로, 실제 호출에서 rA는 tA가 복사된다. 이때 수행되는 복사에 A의 복사 생성자가 쓰이는 것이다. ‘값에 의한 전달(pass-by-value)’은 ‘복사 생성자 호출’이라고 이해하면 된다. (하지만 사용자 정의 타입을 값으로 넘기는 발상은 일반적으로 좋지 않다고 알려져 있다. 그보다는 ‘상수 객채에 대한 참조로 넘기기’가 더 좋다)
STL은 표준 템플릿 라이브러리(Standard Template Library)의 준말이다. 이것은 C++의 표준 라이브러리에 속해 있는 라이브러리인데 컨테이너(vector, list, set, map 등), 반복자(vector
자바 혹은 C#을 하다가 C++을 배우려는 프로그래머들이 당황하는 부분이 미정의 동작(undefined behavior)이다. C++에서 사용되는 구문 요소들 중 몇 개는 이런저런 이유로 인해 동작 자체가 글자 그대로 ‘정의되어 있지 않다’. 쉽게 말해, 실행 시간에 어떤 현상이 발생할지 확실히 예측할 수 없다는 뜻이다. 다음은 해당 상황에 대한 예제 코드 2가지이다.
int *p = 0; // p는 널 포인터이다.
std::cout << *p; // 널 포인터를 역참조하면 미정의 동작이 발생한다.
char name[] = "GYUT"; // name은 크기가 4인 배열이다.
char a = name[10]; // 유효하지 않은 배열 원소지정번호(index)로 참조하면 미정의 동작이 발생한다.
다른 언어로 생활을 하다가 C++로 온 프로그래머들이 헷갈려 하는 용어가 하나 더 있다. 바로 인터페이스(interface)이다. 자바 및 닷넷 계열 언어의 경우에는 Interface라는 것이 언어 차원에서 주어져 있지만, C++에는 그런게 없다. C++로도 인터페이스와 흡사하게 만들 수는 있다. 공부를 진행해가면서 앞으로 말하는 인터페이스는 지극히 평범하고 일반적인 설계 아이디어로서의 인터페이스다.
다음은 사용자(client)라는 용어이다. 이 책에서 ‘사용자’는 직접 만든 코드(인터페이스가 되겠다)를 사용하는 아무개 혹은 아무 것이다. 예를 들어 함수의 사용자는 그 함수를 사용하는 모든 대상이 해당된다. 그 함수를 호출하는(혹은 주소를 취하는) 코드, 그런 코드를 작성했거나 유지보수하는 사람 모두가 사용자이다. 클래스 혹은 템플릿의 사용자는 그 클래스/템플릿을 사용하는 소프트웨어의 코드도 되고, 그 코드를 작성하고 유지보수하는 프로그래머도 된다.
코드 주석문에서 생성자와 소멸자를 언급할 때는, 약자표기인 ctor 및 dtor를 사용한다.
스레딩에 대한 고려사항
C++은 언어 차원에서 스레드에 대한 개념 자체가 없다. 어떤 종류의 병행성도 고려하고 있지 않은 언어이다. C++의 표준 라이브러리도 마찬가지이다. C++이 관여하고 있는 한, 다중스레드 프로그램이라는 것은 존재하지 않는다. 하지만 실질적으로 그렇지 않은게 우리의 일상이기도 하다. 하지만 이번 공부에서는 최대한 다중스레드를 생각하지 않고, 단일 스레드 위주로 진행할 것이다.
TR1 그리고 부스트
TR1(“Technical Report 1”)
- C++ 표준 라이브러리에 새로 추가되는 기능들에 대한 명세이다. 새로 추가된 기능들은 클래스 및 함수 템플릿이 주류인데, 해시 테이블(hash table), 참조 카운팅 방식 스마트 포인터(reference-counting smart pointer), 정규 표현식(regular expression) 등이다. TR1의 구성요소는 tr1 네임스페이스에 들어있고, tr1 네임스페이스는 std 네임스페이스 안에 중첩되어 있다.
부스트
- 비슷한 공력의 고수들 사이에서 교차 검증을 거쳤고, 플랫폼 간 이식성도 가진 오픈소스 C++ 라이브러리를 제공하는 단체이다. 이 단체의 웹사이트(http://boost.org)를 가리키기도 한다. TR1의 구성요소 중 대부분은 바로 이곳에서 만들어졌다.
Leave a comment