Effective C++

Updated:

용어 사용에 대하여

선언(declaration)은 코드에 사용되는 ‘어떤 대상’의 이름과 타입을 컴파일러에게 알려 주는 것이다. 하지만 구체적인 세부사항은 선언에 들어 있지 않다.

  • 객체 선언
    • extern int A;
  • 함수 선언
    • std::size_t A(int B);
  • 클래스 선언
    • class A;
  • 템플릿 선언
    • template class GraphNode;

모든 함수의 선언문에는 시그니처(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::iterator, set::iterator 등), 알고리즘(for_each, find, sort 등) 및 이들과 관련된 기능들이 집결한 결정체이다. 여기서 컨테이너/반복자/알고리즘에 관련된 기능들의 상당 부분을 이른바 **함수 객체(function object)**, 즉 함수처럼 동작하는 C++ 객체가 차지하고 있다. 이 함수 객체는 함수 호출 연산자인 operator()를 오버로드한 클래스로 만든다.

​

자바 혹은 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의 구성요소 중 대부분은 바로 이곳에서 만들어졌다.

Categories:

Updated:

Leave a comment