제 2 장: 들어가는 말

C++ 주해서는 매년 그로닝겐(Groningen) 대학에서 C/C++ 프로그래밍 강좌를 위해 필자가 실제로 사용하는 교재이다. 이 책은 완전한 C/C++ 참고서가 아니다. C++의 근간이 되는 C-배경은 대부분 다루지 않는다. 그에 관해서는 다른 자원을 참조하거나 (예를 들어 독일어판 De programmeertaal C, Brokken and Kubat, University of Groningen, 1996) 아니면 조지 단체프(George Danchev (danchev at spnet dot net))가 필자에게 소개해 준 온라인-책을 추천한다.

독자에게 미리 주의를 주는 바이다. 여러분이 C 프로그래밍 언어를 실제로 완전히 익혔다고 간주한다. C++ 주해서는 C 프로그래밍 언어가 끝나는 부분부터 시작한다. 다시 말해, 포인터라든가 기본적인 제어 흐름 그리고 함수 구조부터 시작한다.

어떤 요소들은 이 책에서 다루지 않는다. ({ 대신에 ??<를 사용하거나 } 대신에 ??>를 사용하는)) 세겹기호 그리고 ( [ 대신에 <:를 사용하거나 ] 대신에 >:를 사용하는) 두겹기호등과 같은 특정한 어휘 토큰은 이 책에서 다루지 않는다. 이런 토큰들은 C++ 소스 코드에 거의 등장하지 않기 때문이다. 게다가 세겹기호는 앞으로 C++17 표준에서 제거될 것이 거의 확실하다.

이 책의 버전 번호는 내용이 바뀔 때마다 갱신된다 (현재 10.9.0). 맨 앞의 번호는 핵심 번호이고 아마도 당분간 변경되지 않을 것이다. 크게 개작되었음을 의미하기 때문이다. 가운데 번호는 새로 정보가 추가되면 증가한다. 마지막 번호는 작은 변경이 있었음을 알린다. 오자나 탈자가 교정되면 마지막 번호가 증가한다.

이 책은 네델란드에 있는 그로닝겐 대학의 정보 기술 센터(Center of Information Technology)에서 출간하였다. GNU 공중 공개 라이선스의 보호를 받는다.

이 책의 식자는 yodl 포맷팅 시스템에 의하여 처리되었다.

이 책에 관하여 제안이나 추가하고 싶은 것 또는 개선하고 싶은 것이 있다면 모두 저자에게 알려 주시기 바란다.

Frank B. Brokken
Center of Information Technology,
University of Groningen
Nettelbosje 1,
P.O. Box 11044,
9700 CA Groningen
The Netherlands
(email: f.b.brokken@rug.nl)

이 장은 C++에 정의된 특징을 대략 살펴본다. 몇가지 C 확장을 검토하고 객체 기반의 프로그래밍 그리고 객체 지향 프로그래밍의 개념을 간략하게 소개한다.

2.1: C++ 주해서의 성장사

이 절은 버전 번호의 첫번째 또는 두번째 번호가 바뀔 때 수정된다 (가끔은 세번째 번호가 바뀔 때에도 수정된다). 크게 개정되더라도 이전의 표제어들은 유지된다. 하지만 예전 배포본을 참조하는 표제어들은 제거된다. (역주: 본 역서는 완전히 폐기된 버전이 아니면 삭제하지 않고 그대로 두는 것을 원칙으로 한다)

2.2: C++의 역사

C++의 첫 구현이 1980년대에 유닉스 운영 체제가 탄생한 AT&T Bell 연구소에서 개발되었다.

C++는 원래 `프리-컴파일러'로서 C의 프리-프로세서와 비슷하다. 소스 코드에서 특별한 구조를 평범한 C로 변환한다. 그 다음에 이 코드는 표준 C 컴파일러로 컴파일되었다. C++ 프리-컴파일러가 읽어 들이는 `프리-코드'는 일반적으로 확장자가 .cc, .C 또는 .cpp인 파일에 위치했다. 이 파일은 그러면 확장자가 .cC 소스 파일로 변환되었다. 그 위에 컴파일되고 링크되었다.

C++ 소스 파일의 유산은 살아 남아서 확장자 .cc.cpp가 여전히 사용된다. 그렇지만, C++ 프리-컴파일러의 선작업은 오늘날 실제 컴파일 시간에 수행된다. 종종 컴파일러는 확장자를 보고 소스 파일에 사용된 언어를 식별한다. 이 사실은 볼랜드사와 마이크로소프트사의 C++ 컴파일러에도 적용된다. C++ 소스 파일의 확장자가 .cpp라고 간주한다. Gnu 컴파일러인 g++은 오늘날 많은 유닉스 플랫폼에서 사용할 수 있다. 이 컴파일러는 C++의 확장자를 .cc라고 간주한다.

C++ 언어가 한 때 C 코드로 컴파일되었다는 사실은 C++ 언어가 C 언어의 상위 집합이라는 사실에서도 볼 수 있다. C++C 문법을 거의 완벽하게 제공하고 모든 C-라이브러리 함수를 지원하며 거기에 C++만의 특징을 추가한다. 이 덕분에 C에서 C++로 손쉽게 이전할 수 있다. C에 익숙한 프로그래머라면 .c 확장자 대신에 확장자가 .cc.cpp인 소스 파일을 사용하여 `C++ 프로그래밍'을 시작하면 된다. 그러면 C++가 제공하는 모든 가능성을 매끄럽게 익힐 수 있다. 익숙한 습관을 갑자기 바꾸지 않아도 된다.

2.2.1: C++ 주해서의 탄생

C++ 주해서의 최초 버전은 프랭크 브로켄(Frank Brokken)과 카렐 쿠바트(Karel Kubat)가 독일어로 LaTeX를 사용하여 작성했다. 시간이 지나자 카렐은 1994년 9월 (당연히) 영어로 본문을 재작성했다.

이 가이드의 첫 버전이 1994년 10월에 인터넷에 올라왔다. 그 때쯤 SGML로 변환되었다.

점차 장들이 새로 추가되고 내용이 변경되어 더욱 개선되었다 (아낌없이 의견을 보내 준 독자 여러분 덕분이다).

버전 4에서 프랭크는 새 장들을 추가했고 문서를 SGML에서 yodl로 변환했다.

C++ 주해서는 자유롭게 배포해도 된다. 법률 고지를 꼭 읽어 보시기 바란다.

이 시점을 넘어서 계속 읽고 계시다면 해당 법률 고지를 인지하고 있고 동의한다는 뜻이다.

이 문서가 마음에 들면 친구들에게 소개하라. 필자에게 이메일을 보내 알려 주면 더 좋다.

2.2.2: C++ 컴파일러를 사용하여 C 프로그램 컴파일하기

초보 C++ 프로그래머라면 C++ 언어가 C 언어의 완벽한 상위 집합은 아니라는 사실을 깨닫아야 한다. 단순히 파일 이름에서 확장자를 .cc로 바꾸고 그 파일을 C++ 컴파일러를 통하여 실행하면 몇 가지 차이를 만나게 될 것이다.

2.2.3: C++ 프로그램 컴파일하기

C++ 프로그램을 컴파일하려면 C++ 컴파일러가 필요하다. 이 책이 무료임을 감안하면 당연히 무료 컴파일러를 추천한다. 자유 소프트웨어 재단(Free Software Foundation (FSF))은 http://www.gnu.org에 무료 C++ 컴파일러를 제공한다. 이 컴파일러는 무엇보다도 데비안 (http://www.debian.org) 리눅스 배포본에 포함되어 있다 (http://www.linux.org).

C++14 표준의 특징을 사용하려면 컴파일러에 --std=c++14 옵션을 제공해야 한다. C++17 표준을 활성화하려면 --std=c++17 옵션을 지정하라. C++ 주해서는 예제를 컴파일할 때 이 옵션이 사용된다고 간주한다.

2.2.3.1: MS-Windows에서의 C++

MS-Windows 용으로 시그너스사(Cygnus)가 (http://sources.redhat.com/cygwin) Gnu g++ 컴파일러의 윈도우즈 이식본을 위한 토대를 제공한다.

무료 g++ 컴파일러를 얻기 위해 위의 URL에 방문하면 install now 버튼을 클릭하라. 이렇게 하면 setup.exe 파일을 내려 받는데, 이 파일을 실행하면 cygwin을 설치할 수 있다. 설치될 소프트웨어는 인터넷으로부터 setup.exe를 통하여 내려 받을 수 있다. 다른 방법도 있다 (예를 들어, CD-ROM을 이용). 다른 방법은 Cygwin 페이지에 기술되어 있다. 설치는 대화식으로 진행된다. 제안하는 기본값이 합리적이며 별 다른 이유가 없다면 받아 들이는 편이 좋다.

최신의 Gnu g++ 컴파일러는 http://gcc.gnu.org에서 얻을 수 있다. Cygnus 배포판에서 얻은 컴파일러가 최신 버전에 비해 느린 감이 있다면 최신 버전의 소스를 내려 받아 이미 있는 컴파일러로 새 컴파일러를 빌드할 수 있다. (위에 언급한) 컴파일러의 웹 페이지를 보면 처리하는 법을 자세하게 볼 수 있다. 필자의 경험에 의하면 새 컴파일러를 Cygnus 환경에서 빌드하면 깔끔하게 작동한다.

2.2.3.2: C++ 소스 컴파일

일반적으로 다음 명령어를 사용하면 C++ 소스 파일 `source.cc'을 컴파일할 수 있다.
        g++ source.cc
이렇게 하면 이진 프로그램이 생산된다 (a.out 또는 a.exe). 기본 이름이 마음에 안들면 -o 플래그를 사용하여 실행 파일의 이름을 지정할 수 있다 (여기에서는 프로그램을 source로 생산함):
        g++ -o source source.cc

단순히 컴파일만 필요하면 컴파일된 모듈은 다음 -c 옵션으로 생산할 수 있다.

        g++ -c source.cc
이렇게 하면 파일 source.o가 생성된다. 나중에 다른 모듈에 링크할 수 있다. 지적했듯이 컴파일러 옵션으로 --std=c++11 (주의: 옆줄 두 개)를 제공하면 C++11 표준의 특징을 활성화할 수 있다. 또 --std=c++14를 지정하면 C++14 표준의 특징이 활성화된다. --std=c++17을 지정하면 더 최신의 C++17 표준을 사용하여 소스를 컴파일할 수도 있다 (권장함).

C++ 프로그램은 순식간에 너무 복잡해져서 `수작업'으로 관리할 수 없다. 무게감 있는 모든 프로그래밍 프로젝트에 프로그램 관리 도구가 사용된다. 표준적인 make 프로그램을 사용하여 C++ 프로그램을 관리하는 것이 보통이지만 좋은 대안이 있다. 예를 들어 icmake 또는 ccbuild 프로그램 관리 유틸리티가 있다.

C++를 연구하면서 관리 유틸티리를 사용하시기를 적극 권고한다.

2.2.3.3: C++14

2014년 10월 18일 ISO/IEC 표준 위원회는 C++에 관하여 C++ 표준을 승인했다. 이른바 C++14로서 C++11 표준을 개선했고 버그 수정이 이루어졌다. C++14의 작업 초안은 자유롭게 볼 수 있고 여기에서 내려 받을 수 있다. C++14 표준을 Gnu의 g++ 컴파일러에 활성화시키려면 --std=c++14를 지정하면 된다.

C++ 주해서는 C++14 표준에 관련된 변경사항들을 반영했다. C++14 표준이 언급되는 곳이면 인덱스에서 C++14 항목을 참고하라.

현재 표준은 C++17이다. C++ 주해서의 여러 곳에서 C++17 표준을 언급한다. C++ 주해서의 인덱스를 보면 C++17에 적절한 항목으로 빠르게 갈 수 있다. C++17 표준을 Gnu의 g++ 컴파일러에 활성화시키려면 --std=c++17을 지정하라.

2.3: C++: 장점과 주장

C++로 프로그래밍하면 `더 좋은' 프로그램을 만들 수 있다고 한다. C++는 다음과 같은 장점을 내세운다. 이 가정 중에 어느 것이 옳은 것인가? 언뜻 보면 C++ 언어가 약간 과대 평가되어 있다는 느낌이다. 객체-지향 프로그래밍 접근법도 같은 느낌이 든다. C++ 언어를 향한 열정은 한 때 Lisp이나 Prolog과 같은 인공-지능 (AI) 언어에 쏟던 가정을 닮았다. 이 언어들은 대단히 어려운 AI-문제들을 `거의 아무 노력 없이' 해결하도록 되어 있었다. 새로운 언어들은 종종 과장된다. 결국, 어떤 문제든 프로그래밍 언어에 상관없이 코딩이 가능하다 (BASIC이나 기계어로도 가능하다). 주어진 프로그래밍 언어의 장점과 단점은 `그것을 가지고 무엇을 할 수 있는가'가 아니라 `프로그래밍 문제를 효율적이고 이해하기 쉽게 해결하는데 프로그래밍 언어가 어떤 도구를 제공하는가이다'. 종종 이런 도구들은 구문적 형태에 제한을 두고 어떤 구조를 강제하거나 권장하곤 한다. 아니면 그런 구문적 형태를 적용함으로써 즉, '수용함으로써' 의도를 보여준다. 평범한 어셈블리 명령어로 구성된 기다란 리스트 대신에 이제 흐름 제어 서술문이나 함수 또는 객체 혹은 이른바 템플릿을 사용하여 코드를 구성하고 조직한다. 프로그래머는 선택한 언어로 `우아하게' 뜻을 표현한다.

그렇지만 위의 C++와 관련하여 다음과 같이 지원한다.

물론 C++가 모든 프로그래밍 문제를 해결하는 (일반적인 OOP의) 만능 언어는 아니다. 그렇지만 C++가 다양하게 제공하는 새롭고 우아한 편의기능들은 살펴 볼 가치가 있다. 단점으로는 C++ 문법의 복잡도가 C에 비하여 심각하게 상승했다는 것이다. 이것을 심각한 단점으로 간주할 수 있다. 시간이 지나면 이렇게 증가된 복잡성에 익숙해지겠지만 거기까지 이르려면 시간이 많이 걸리고 고통스러울 것이다.

C에서 C++로 이전할 때 이 책이 독자들에게 도움이 되기를 바란다. C에 비하여 C++에 추가된 것들에 초점을 두고 평범한 C는 다루지 않았다. 모쪼록 독자 여러분이 이 책을 좋아하고 혜택을 누리시기를 바라는 바이다.

C++로의 여행을 즐기시고 좋은 결과를 얻으시기를 바란다!

2.4: 객체 지향 프로그래밍이란 무엇인가?

객체 지향 (객체-기반) 프로그래밍은 C 프로그램에서 사용하는 전략과 약간 다르게 프로그래밍 문제에 접근한다. C 프로그래밍 문제는 `절차적 접근법'으로 해결한다. 문제는 작은 문제로 분해되고 이 과정이 최소 작업으로 코드할 수 있을 때까지 반복된다. 그리하여 함수가 한 가득 만들어진다. 인자와 변수 그리고 전역 변수와 지역 변수를 (또는 static 변수를) 통하여 통신한다.

이와 대조적으로 객체 기반의 접근법은 문제 서술에 사용된 핵심어( keywords)들을 식별한다. 이런 핵심어들은 다이어그램에 묘사되고 화살표를 핵심어 사이에 그려서 내부적인 계통도를 묘사한다. 핵심어들은 구현에서 객체가 되고 계통도로 이 객체들 사이의 관계가 정의된다. 객체라는 용어로 정의가 잘된 구조체를 기술한다. 여기에 한 객체에 관한 모든 정보를 즉, 데이터 유형과 그 데이터를 조작할 함수들을 담는다. 객체 지향 접근법의 예를 들어 보자:

위의 월급 관리를 표현한다면 키워드는 mechanics, owner, salesmen 그리고 purchasers가 될 것이다. 각 특성은 다음과 같다. 월급과 구매나 판매에 따른 보너스 그리고 출장 경비 보상이 있다. 이런 방식으로 문제를 분석하면 다음과 같은 표현에 다다른다. 식별된 객체들 사이의 계통도는 그림 1에 더 자세하게 묘사된다.

Figure 1 is shown here.
그림 1: 판매 관리 객체 계통도.

위와 같은 계통도를 정의하는데 있어서 전반적인 처리는 가장 간단한 유형을 기술하면서 시작한다. 전통적으로 더 복잡한 유형은 기본 집합으로부터 파생시킨다 (객체 지향 언어에서 여전히 인기가 있다). 파생시킬 때마다 약간의 기능을 추가한다. 전체 문제가 표현될 때까지 이 파생 유형으로부터 더 복잡한 유형을 무한대로 파생시킬 수 있다. 그렇지만 해가 갈 수록 이런 접근법은 C++에서 인기를 잃고 있다. 전형적으로 결합도가 과도하게 높아져서, 복잡한 프로그램의 이해와 유지 관리 그리고 테스트 가능성을 개선하기는 커녕 오히려 줄이는 결과가 되었기 때문이다. C++에서 객체 지향 프로그램은 점점 더 작고 이해하기 쉬운 계통도와 절제된 결합 그리고 디자인 패턴이 주요한 역할을 하는 개발 과정이 인기를 점점 더 끌게 되었다 (참조 Gamma et al. (1995)).

그럼에도 C++에서 클래스는 객체의 특징을 정의하는데 자주 사용된다. 클래스는 유용한 일을 하기 위해 필요한 기능을 포함한다. 클래스는 일반적으로 다른 클래스의 객체에 모든 기능을 제공하지는 않는다 (그리고 전형적으로 자신의 데이터는 제공하지 않는다). 앞으로 보듯이 클래스는 바깥 세계에서 직접적으로 수정할 수 없도록 자신의 특성을 숨긴다. 대신에 전용 함수를 사용하여 객체의 특성에 접근하거나 수정한다. 그리하여 클래스-유형의 객체는 자신의 정합성을 유지한다. 여기에서 핵심 개념은 캡슐화이다. 데이터 은닉은 그저 한 예에 불과하다. 이런 개념은 제 7 장에 더 자세히 설정한다.

2.5: C와 C++ 사이의 차이

이 절은 C++ 예제 코드를 보여주고 CC++ 사이의 차이점을 강조한다.

2.5.1: `main' 함수

C++에는 main 함수가 두 가지 형태만 있다. 하나는 int main()이고 다른 하나는 int main(int argc, char **argv)이다.

주의:

2.5.2: 줄끝 주석

ANSI/ISO 정의에 의하면, `줄끝 주석은 C++ 구문으로 구현되었다. 이 주석은 //으로 시작하고 줄끝 표식에서 끝난다. 표준 C 주석은 /**/으로 구분되는데 여전히 C++에 사용된다.
    int main()
    {
        // 이것은 줄끝 주석이다.
        // 한 줄 짜리 주석

        /*
            이것은 표준-C 주석이다. 
            여러 줄에 걸친다.
        */
    }
예제는 이렇지만, C++ 함수 안에 C 형식의 주석은 사용하지 않기를 권한다. 가끔 기존의 코드를 임시로 억제해야 할 경우가 있다. 예를 들어 테스트를 하기 위해서라면 표준 C 주석을 사용할 수 있으며 아주 실용적이다. 그렇게 억제된 코드에 자체로 또 그런 여러 줄 주석이 들어 있다면, 주석 줄이 내포되어 있기 때문에 컴파일 에러가 일어날 것이다. 그러므로 지켜야할 규칙은 C 유형의 주석을 C++ 함수 안에 사용하지 않는 것이다 (물론 다른 방법으로 #if 0부터 #endif까지 프리-프로세서 지시어 쌍을 사용해도 된다).

2.5.3: 엄격한 유형 점검

C++는 아주 엄격하게 유형을 점검한다. 함수마다 호출하기 전에 먼저 원형을 알려 주어야 하고 호출은 그 원형에 부합해야 한다. 다음 프로그램은
    int main()
    {
        printf("Hello World\n");
    }
C 아래에서는 printf() 함수를 알지 못한다는 경고가 따르지만 컴파일은 된다. 그러나 C++ 컴파일러는 (당연히) 코드를 만들어 내지 못한다. 물론 이 에러는 #include <stdio.h>가 없기 때문에 야기된다 (C++에서는 #include <cstdio> 지시어로 포함된다).

그와 동시에 이미 보았듯이 C++에서 main 함수는 언제나 int반환 값으로 사용한다. 물론 명시적으로 return 서술문을 정의하지 않아도 int main()를 정의할 수 있지만 main 안에서 명시적인 int-표현식 없이 return 서술문을 사용하는 것은 안된다. 예를 들어:

    int main()
    {
        return;     // 컴파일되지 않는다. int 표현식을 기대한다.
                    // 예를 들어, return 1;
    }

2.5.4: 함수 중복정의

C++에서 이름은 동일하지만 다르게 행위하는 함수를 정의하는 것이 가능하다. 함수는 매개변수 리스트가 달라야 한다 (더불어 const 속성에 따라 달라진다). 아래에 예제를 보여준다.
    #include <stdio.h>

    void show(int val)
    {
        printf("Integer: %d\n", val);
    }

    void show(double val)
    {
        printf("Double: %lf\n", val);
    }

    void show(char const *val)
    {
        printf("String: %s\n", val);
    }

    int main()
    {
        show(12);
        show(3.1415);
        show("Hello World!\n");
    }

위의 프로그램에서 show라는 이름으로 함수 세 개가 정의되는데, 매개변수 리스트만 다르고 이름이 같다. 각각 intdouble 그리고 char *를 기대한다. 이름은 같지만 매개변수 리스트가 다른 함수를 중복정의(overloaded) 되었다고 부른다. 그런 함수를 정의하는 행위를 `함수 중복정의'라고 부른다.

C++ 컴파일러는 함수 중복정의를 약간 간단한 방식으로 구현한다. (예제에서 show를 공유하듯이) 함수는 이름을 공유하지만 컴파일러 (링커)는 완전히 다른 이름을 사용한다. 소스 파일의 이름을 내부적으로 사용되는 이름으로 변환하는 것을 `이름 변조(name mangling)'라고 부른다. 예를 들어 C++ 컴파일러는 원형 void show (int)를 내부 이름 VshowI로 변환할 수 있지만 반면에 char * 인자를 가진 비슷한 함수는 VshowCP로 부를 수 있다. 내부적으로 사용되는 실제 이름은 컴파일러가 결정한다. 이런 이름이 라이브러리 목록에 나타날 때를 제외하고는 프로그래머가 신경쓸 일이 아니다.

함수 중복정의에 관련하여 추가로 한 마디 하면:

2.5.5: 기본 함수 인자

C++에서 함수를 정의할 때 `기본 인자'를 줄 수 있다. 기본 인자를 프로그래머가 지정하지 않으면 컴파일러가 대신 공급한다. 예를 들어:
    #include <stdio.h>

    void showstring(char *str = "Hello World!\n");

    int main()
    {
        showstring("Here's an explicit argument.\n");

        showstring();           // 실제로 다음과 같다.
                                // showstring("Hello World!\n");
    }
기본 인자를 정의한 곳에는 인자를 생략하더라도 아무 문제가 없다. 명시적으로 인자를 지정하지 않고 호출하면 빠진 인자를 공급하는 책임을 컴파일러가 진다. 기본 인자를 사용했다고 코드가 더 짧아지는 것도 아니고 더 효율적인 것도 아니다.

기본 인자를 여럿 정의할 수 있다.

    void two_ints(int a = 1, int b = 4);

    int main()
    {
        two_ints();            // 인자:  1, 4
        two_ints(20);          // 인자: 20, 4
        two_ints(20, 5);       // 인자: 20, 5
    }
two_ints 함수를 호출하면 컴파일러는 하나 또는 두 개의 인자를 필요에 따라 공급한다. 그렇지만 two_ints(,6)과 같은 서술문은 허용되지 않는다. 다시 말해, 인자를 생략한다면 반드시 오른쪽부터 생략해야 한다.

기본 인자는 컴파일 시간에 알려져야 한다. 그 시점에 인자가 함수에 공급되기 때문이다. 그러므로 기본 인자는 함수를 구현할 때가 아니라 선언할 때 지정해야 한다.

    // 샘플 헤더 파일
    extern void two_ints(int a = 1, int b = 4);

    // 예를 들어 two.cc에 있는 함수 코드 
    void two_ints(int a, int b)
    {
        ...
    }
함수를 정의할 때 기본 인자를 주는 것은 에러이다. 함수를 다른 소스가 사용할 때 컴파일러는 함수 정의가 아니라 헤더 파일을 읽기 때문이다. 결과적으로 컴파일러는 함수의 기본 인자 값을 결정할 방법이 없다. 현재 컴파일러는 함수 정의에서 기본 인자를 탐지하면 컴파일 시간 에러를 일으킨다.

2.5.6: NULL-포인터 vs. 0-포인터 그리고 nullptr

C++에서 0 값은 모두 0으로 코딩된다. C에서 NULL은 종종 포인터의 문맥에서 사용된다. 이 차이는 순전히 스타일의 문제이다. 그렇지만 널리 채택된 스타일이다. C++에서 NULL은 피해야 한다 (마크로이기 때문인데, 마크로는 C++에서 손쉽게 피할 수 있다 --그러므로 그렇게 하는 것이 좋다-- 8.1.4항). 대신에 0은 거의 언제나 사용할 수 있다.

그러나 거의 언제나이지 언제나는 아니다. C++는 함수 중복정의를 허용하므로 프로그래머는 2.5.4항에 보여준 상황에서 예상치 못한 함수 선택에 직면할 가능성이 있다.

    #include <stdio.h>

    void show(int val)
    {
        printf("Integer: %d\n", val);
    }

    void show(double val)
    {
        printf("Double: %lf\n", val);
    }

    void show(char const *val)
    {
        printf("String: %s\n", val);
    }

    int main()
    {
        show(12);
        show(3.1415);
        show("Hello World!\n");
    }

이 상황에서 프로그래머가 show(char const *) 함수를 호출할 생각이라면 show(0) 함수를 호출할 것이다. 그러나 이것은 작동하지 않는다. 0이 int로 해석되고 그래서 show(int)가 호출된다. 그러나 show(NULL)를 호출해도 역시 작동하지 않는다. C++NULL을 0으로 정의하지 ((void *)0)으로 정의하지 않기 때문이다. 그래서 다시 또 show(int)가 호출된다. 이런 종류의 문제를 풀기 위하여 C++ 표준으로 nullptr 키워드가 새로 도입되었다. 0 포인터를 나타낸다. 현재 예제에서 프로그래머는 show(nullptr)을 호출해야만 올바르게 함수를 선택할 수 있다. nullptr 값은 또 포인터 변수를 초기화하는 데에도 사용할 수 있다. 예를 들어,

    int *ip = nullptr;      // OK
    int value = nullptr;    // 에러: 값이 포인터가 아님

2.5.7: `void' 매개변수 리스트

예를 들어 C에서 다음과 같이 매개변수가 비어 있는 함수 원형은
    void func();
선언된 함수의 원형에 인자 리스트가 없다는 뜻이다. 그러므로 이런 원형을 사용하는 함수에 대하여 컴파일러는 인자에 상관없이 func를 호출하더라도 경고하지 않는다. C에서 함수를 전혀 인자 없이 명시적으로 선언하고 싶다면 다음과 같이 void 키워드가 사용된다.
    void func(void);
C++는 정적 유형 점검을 강제하기 때문에 빈 매개변수 리스트는 매개변수가 완전히 없음을 나타낸다. 그러므로 void 키워드는 생략된다.

2.5.8: `#define __cplusplus'

ANSI/ISO 표준을 따르는 C++ 컴파일러는 __cplusplus 심볼이 정의되어 있다. 마치 소스 파일마다 앞에 #define __cplusplus 전처리기 지시어가 붙은 것처럼 말이다.

이 심볼의 사용법을 다음 항에서 살펴 보겠다.

2.5.9: 표준 C 함수 사용하기

실행 시간 라이브러리에 컴파일해 모아 놓은 정상적인 C 함수도 C++ 프로그램에 사용할 수 있다. 그렇지만 그런 함수는 반드시 C 함수로 선언해야 한다.

예를 들어 다음의 조각 코드는 xmalloc 함수를 C 함수로 선언한다.

    extern "C" void *xmalloc(int size);
이 선언은 C에서의 선언과 유사하다. 원형 앞에 extern "C"가 붙는다는 점만 제외하면 말이다.

약간 다른 방식으로 C 함수를 선언하는 방법은 다음과 같다.

    extern "C"
    {
        // C-선언은 여기에 둔다.
    }
전처리 지시어를 선언의 위치에 놓아도 된다. 예를 들어 C 언어 myheader.h 헤더 파일에 C 함수가 선언되어 있다면 다음과 같이 C++ 소스 파일에 포함시킬 수 있다.
    extern "C"
    {
        #include <myheader.h>
    }
두 방법을 모두 사용할 수 있지만 실제로는 C++ 소스 파일에서 만날 일이 거의 없다. 외부의 C 함수를 선언하기 위해 더 자주 사용되는 방법은 다음 항에서 만난다.

2.5.10: C와 C++ 모두에 적용되는 헤더 파일

미리 정의된 __cplusplus 심볼과 extern "C" 함수를 조합하면 CC++ 모두를 위해 헤더 파일을 만들 수 있다. 헤더 파일에 CC++ 프로그램 모두에 사용될 일군의 함수를 선언할 수 있다.

그런 헤더 파일 설정은 다음과 같다.

    #ifdef __cplusplus
    extern "C"
    {
    #endif

        /* C-데이터와 함수 선언은 여기에 삽입한다. 예를 들어, */
        void *xmalloc(int size);

    #ifdef __cplusplus
    }
    #endif
이 설정을 사용하여 정상 C 헤더 파일은 파일의 상단에서 extern "C" {와 그리고 파일 하단에서 }로 둘러 싸인다. #ifdef 지시어는 C인지 C++인지 컴파일의 종류를 테스트한다. `표준' C 헤더 파일, 예를 들어 stdio.h같은 파일은 이런 식으로 구성되었고 그러므로 CC++에 모두 사용할 수 있다.

그리고 C++ 헤더는 삽입 보호기능을 제공해야 한다. C++에서 같은 소스 파일에 같은 헤더 파일을 두 번 포함하는 것은 바람직하지 않다. 헤더가 중복 삽입되는 문제는 헤더 파일에 #ifndef 지시어를 사용하면 쉽게 피할 수 있다. 예를 들어:

    #ifndef MYHEADER_H_
    #define MYHEADER_H_
        // #ifdef __cplusplus 등등의 지시어를 사용하여
        // 헤더 파일의 선언은 여기에 삽입된다,
    #endif
이 파일을 전처리기가 처음 스캔할 때 심볼 MYHEADER_H_는 아직 정의되어 있지 않다. #ifndef 조건은 성공하고 모든 선언이 스캔된다. 그리고 심볼 MYHEADER_H_가 정의된다.

같은 소스 파일을 컴파일하는 동안 다시 이 파일을 스캔할 때 심볼 MYHEADER_H_가 이미 정의되어 있기 때문에 결과적으로 컴파일러는 #ifndef#endif 사이의 모든 정보를 버린다.

이 문맥에서 심볼 이름 MYHEADER_H_는 인지의 목적으로만 기여한다. 그러려면 헤더 파일의 이름을 대문자로 하고 점 대신에 밑줄 문자를 사용하면 된다.

이 외에도 관례는 C 헤더 파일에 확장자 .h를 주고 C++ 헤더 파일에 확장자를 부여하지 않도록 진화하였다. 예를 들어 표준 iostream으로서 cincout 그리고 cerriostream.h가 아니라 iostream 헤더를 포함한 후에야 사용할 수 있다. 이 책은 표준 C++ 헤더 파일에 이 관례를 사용하지만 모든 곳에 꼭 그렇게 하는 것은 아니다.

헤더 파일에 관하여 할 말이 아직 많이 남아 있다. 7.11절C++ 헤더 파일을 조직하는 방법을 깊이 연구한다.

2.5.11: 지역 변수를 정의하기

C에서 지역 변수는 오직 함수의 상단 또는 내포 블록의 시작 부분에만 정의할 수 있다. C++에서 지역 변수는 코드 어디에든 생성할 수 있다. 심지어 서술문 사이에도 가능하다.

게다가 지역 변수는 사용하기 바로 전에 서술문 안에 정의할 수 있다. 전형적인 예는 for 서술문이다.

    #include <stdio.h>

    int main()
    {
        for (int i = 0; i < 20; ++i)
            printf("%d\n", i);
    }
이 프로그램에서 i 변수는 for 서술문의 초기화 부분에 생성된다. ANSI-표준에 따르면 이 변수는 for-서술문보다 먼저 존재하지 않고 for-서술문을 넘어서서 존재하지 않는다. 구형 컴파일러라면 이 변수가 for-서술문 이후에도 생존하는 경우가 있지만 오늘날에는 다음과 같이
warning: name lookup of `i' changed for new ANSI `for' scoping using obsolete binding at `i'
경고: 새로운 ANSI `for' 영역에 `i'의 이름 찾기 방식이 변경됨. 폐기된 바인딩을 `i'에 사용함
변수가 for-회돌이 밖에 사용되면 경고가 따른다.

그 의미는 명백하다. 서술문을 넘어서서 해당 변수를 사용해야 한다면 for-서술문 바로 앞에 정의하라. 그렇지 않으면 변수는 for-서술문 안에 정의해야 한다. 이렇게 되도록이면 생존 영역을 좁혀주는 것은 아주 바람직한 특징이다.

필요할 때 지역 변수를 정의하려면 좀 익숙해져야 한다. 그렇지만 결국 가독성이 더 좋아지고 유지보수가 더 편해지며 더 효율적인 코드를 생산하는 경향이 있다. 지역 변수를 정의할 때 다음과 같은 규칙을 따르기를 권고한다.

잘 생각해 보면, 보조 변수를 지역화하는 데 내포된 블록을 사용할 수 있다. 그렇지만 어떤 경우는 내포된 서술문 안에 지역 변수가 적절하다고 생각되는 상황이 존재한다. 물론 방금 언급한 for 서술문이 해당 사례이지만 지역 변수는 if-else 서술문 안의 조건 절과 switch 서술문 안의 선택 절 그리고 while 서술문 안의 조건 절에도 정의할 수 있다. 그런 식으로 정의된 변수는 내포된 서술문을 포함하여 완전한 서술문에 사용할 수 있다. 예를 들어 다음의 switch 서술문을 연구해 보자:

    #include <stdio.h>

    int main()
    {
        switch (int c = getchar())
        {
            case 'a':
            case 'e':
            case 'i':
            case 'o':
            case 'u':
                printf("Saw vowel %c\n", c);
            break;

            case EOF:
                printf("Saw EOF\n");
            break;

            case '0' ... '9':
                printf("Saw number character %c\n", c);
            break;

            default:
                printf("Saw other character, hex value 0x%2x\n", c);
        }
    }

`c' 문자의 정의가 위치한 곳을 눈여겨보라. switch 서술문의 표현식 부분에 정의되어 있다. 이것은 `c'를 내포된 (하위) 서술문을 포함하여 switch 서술문 에서는 사용할 수 있지만 switch 바깥 영역에서는 사용할 수 없다는 뜻이다.

ifwhile 서술문에도 같은 접근법을 사용할 수 있다. ifwhile 서술문에서 조건 부분에 정의된 변수는 내포된 서술문에서 사용할 수 있다. 그렇지만 약점이 약간 있다.

후자의 지적은 별로 놀라운 일은 아니다. ifwhile 서술문에서 논리 조건을 평가할 수 있으려면 변수의 값이 0(false) 또는 0-아닌 값 (true)으로 해석이 가능해야 한다. 이것은 문제가 되지 않지만 C++에서 객체는 (std::string 유형의 객체 (제 5장)) 종종 함수에 의하여 반환된다. 그런 객체는 숫자 값으로 번역될 수도 있고 안될 수도 있다. 그렇지 않다면 (std::string 객체의 사례와 같이) 그런 변수는 조건 서술문이나 반복 서술문의 조건 또는 표현 절에 정의할 수 없다. 그러므로 다음 예제는 컴파일되지 않는다:
    if (std::string myString = getString())     // getString이
    {                                           // std::string 값을 돌려준다고 간주
        // myString 처리
    }

위의 예제는 설명이 더 필요하다. 변수는 지역 영역에 배치하는 것이 유익하지만 초기화한 다음 곧바로 더 점검할 필요가 있다. 초기화와 테스트모두 하나의 표현식에 조합해 넣을 수 없다. 대신에 내포된 서술문 두 개가 필요하다. 결과적으로 다음 예제도 역시 컴파일되지 않는다.

    if ((int c = getchar()) && strchr("aeiou", c))
        printf("Saw a vowel\n");
이런 상황이 일어나면 내포된 if 서술문을 두 개 사용하거나 아니면 내포된 복합 서술문을 사용하여 int c의 정의를 지역화하라.
    if (int c = getchar())             // 내포된 if-서술문
        if (strchr("aeiou", c))
            printf("Saw a vowel\n");

    {                                  // 내포된 복합 서술문
        int c = getchar();
        if (c && strchr("aeiou", c))
           printf("Saw a vowel\n");
    }

2.5.12: `typedef' 키워드

typedef 키워드는 여전히 C++에서 사용되지만 공용체(union)나 구조체(struct) 또는 열거체(enum)를 정의할 때 더 이상 필수 조건은 아니다. 다음 예제에서 이를 보여준다.
    struct SomeStruct
    {
        int     a;
        double  d;
        char    string[80];
    };
structunion 또는 기타 복합 유형을 정의할 때 이런 유형의 태그를 유형 이름으로 사용할 수 있다 (다음은 위의 예제에 있는 SomeStruct이다):
    SomeStruct what;

    what.d = 3.1415;

2.5.13: 구조체 안에 포함된 함수

C++함수를 구조체의 멤버로 정의할 수 있다. 이제 처음으로 객체의 실전 예제를 만나보자. 앞서 기술하였듯이 (2.4절) 객체는 데이터를 보유한 구조체이다. 그리고 그런 데이터를 조작하기 위해 전문화된 함수가 존재한다.

struct Point의 정의를 아래에 조각 코드로 제공한다. 이 구조체는 int 데이터 필드 두 개와 draw 함수 하나를 선언한다.

    struct Point            // 화면 위의 점 하나 정의
    {
        int x;              // 좌표
        int y;              // x/y
        void draw();        // 그리기 함수
    };
아마도 그리기 프로그램의 구조가 이와 비슷할 것이다. 예를 들어 픽셀을 나타낼 수 있다. 이 구조체(struct)에 관하여 주목할 것은 다음과 같다. Point 구조체는 다음과 같이 사용할 수 있다.
    Point a;                // 화면 위의
    Point b;                // 두 개의 점

    a.x = 0;                // 첫 번째 점을 정의하고
    a.y = 10;               // 그 점을 그린다.
    a.draw();

    b = a;                  // a를 b에 복사
    b.y = 20;               // y-좌표를 재정의하고
    b.draw();               // 그 점을 그린다.
위의 예제에서 보듯이 구조체 안의 함수는 점 (.) 연산자를 사용하여 선택한다 (화살표 (->) 연산자는 객체를 포인터로 사용할 수 있을 때 사용된다). 그러므로 이것은 구조체의 데이터 필드를 선택하는 방식과 동일하다.

이 구문적 생성 방법 뒤에 숨은 아이디어는 여러 유형이 안에 이름이 동일한 함수를 포함할 수 있다는 것이다. 예를 들어 원을 나타내는 구조체는 세 개의 int 값을 포함할 수 있다. 두 개의 값은 원의 중심 좌표를 나타내고 나머지 하나는 반지름을 나타낸다. Point 구조체처럼 이제 Circle도 원을 그리는 draw 함수를 가질 수 있다.

2.5.14: C++17 표준에 도입된 특징들

최신의 C++ 표준은 현재 C++17이다 (C++1z로 지칭하기도 한다). 현재 C++17의 특징을 Gnu g++ 컴파일러 (7.0.0 이후) 버전에 반영하는 작업이 진행 중이다.

작업 초안을 자유롭게 얻을 수 있다. 다음 깃 저장소 https://github.com/cplusplus/draft.git로부터 복제하면 된다.

그 사이에 C++ 주해서는 C++17 표준에 관련된 변화들을 반영할 것이다. 인덱스에서 C++17 항목을 보면 C++17 표준이 언급되어 있는 곳으로 가실 수 있다.

C++17 표준에 다음 특징이 추가되었다.

집합체 초기화 확장

집합체를 (예, structs) 초기화하기 위해 익숙한 활괄호 표기법을 사용할 수 있다. C++17은 초기화 방법을 확장해서 상속을 사용하는 구조체도 활괄호 표기법으로 초기화할 수 있다. 예를 들어:

struct Base
{
    int value;
};
struct Derived: public Base
{
    string text;
    void member();
};

// Derived 객체를 초기화:

Derived der{{value}, "hello world"};
//          -------
//          (첫 번째) 바탕 클래스 초기화.

연산자의 피연산자 평가 순서

C++17에 이르기까지 논리 연산자 andor는 제외하고 이항 연산자의 피연산자를 평가하는 순서는 정의되어 있지 않다. C++17에서 후위 표현식과 할당 표현식 (복합 서술문 포함) 그리고 shift 연산자에 대하여 이를 바꾸었다.

다음 예제에서 firstsecond보다 먼저 평가된다. 그 다음에 third, 그 다음 fourth가 평가된다. 단순 변수나 괄호 표현식 또는 함수 호출이든 상관이 없다.
    first.second
    fourth += third = second += first
    first << second << third << fourth
    first >> second >> third >> fourth
그리고 연산자를 중복정의할 때, 그것을 구현한 함수는 일반적으로 평가되는 순서가 아니라 내장 연산자처럼 평가된다.

컴파일러 속성
컴파일러 속성은 컴파일러가 경고를 보낼 때 프로그래머의 의도를 알린다. 현재 정의된 속성은 다음과 같다.

if constexpr

템플릿 정의에서 if constexpr 서술문 다음의 표현식 값에 따라 함수 정의의 일부를 실체화할 수 있다. 21.16절에 더 자세하게 설명한다.

인라인(Inline) 변수

인라인 함수 말고도 인라인 변수를 여러 번역 단위에 정의할 수 있다 (동일하게 초기화할 수 있다). 예를 들어 헤더 파일에 다음과 같이 담을 수 있다.

    inline int value = 15;                      OK

    class Demo
    {
        // static int s_value = 15;             ERROR
        static int constexpr s_value = 15;      OK

        static int s_inline;                    NEW, 아래 참고
    };
    inline int Demo::s_inline = 20;             OK

`export' 키워드와 `register' 키워드

export 키워드와 register 키워드는 더 이상 사용되지 않는다. 그러나 여전히 식별자로 미래의 사용을 위해 예약되어 있다. 다음과 같이 정의하면

    register int index;
컴파일 에러를 일으킨다. register 키워드도 더 이상 저장소 분류 지정자로 간주되지 않는다 (저장소 분류 지정자는 extern, thread_local, mutable 그리고 static이다).

람다 표현식

람다 표현식이 확장되었다. 18.7절에서 C++17 표식이 붙은 항목들을 찾아 보라.

내포된 이름공간 정의

내포된 이름공간은 영역 지정 연산자를 사용하면 직접적으로 참조할 수 있다. 예를 들어,

namespace Outer::Middle::Inner
{
    // 여기에 있는 개체는 Inner 이름공간에 선언된다/정의된다.
    // Inner 이름공간은 Middle 이름공간에 정의된다.
    // Middle 이름공간은 Outer 이름공간에 정의된다.
}

클래스 템플릿을 위한 템플릿 인자 추론

클래스 템플릿을 위한 템플릿 인자는 컴파일러가 문맥에 따라 추론할 수 있다. 자세한 것은 22.0.1항을 참고하라.