C++ 입문 개요 | 역사, 현대 C++의 변화, 실무에서 쓰이는 분야

C++의 역사와 진화

C에서 C++로

1970년대 초, 벨 연구소의 데니스 리치가 UNIX 운영체제를 작성하기 위해 C 언어를 개발했습니다. C는 “이식 가능한 어셈블리”라고 불릴 만큼 하드웨어에 가까운 제어와 높은 성능을 제공했습니다.

1979년, 같은 벨 연구소의 비야네 스트롭스트룹(Bjarne Stroustrup)이 C에 Simula 스타일의 클래스를 더한 “C with Classes”를 만들기 시작했습니다. 분산 시스템 시뮬레이션처럼 큰 프로그램을 구조적으로 작성하면서도 C의 효율을 잃지 않는 것이 목표였습니다.

1983년에 이 언어의 이름이 C++로 바뀌었습니다. ++는 C의 증가 연산자에서 따온 것으로, “C의 다음 단계”라는 의미입니다.

1985년에는 첫 상용 컴파일러 Cfront와 함께 스트롭스트룹의 책 The C++ Programming Language 초판이 나왔습니다. 표준이 없던 이 시기에는 이 책이 사실상의 언어 명세 역할을 했습니다. Cfront는 C++ 코드를 C 코드로 번역한 뒤 C 컴파일러로 컴파일하는 방식이었고, 이런 출발점은 “쓰지 않는 기능에는 비용을 치르지 않고, 쓰는 기능은 손으로 짠 코드보다 비싸지 않아야 한다”는 C++의 설계 원칙(zero-overhead principle)과 맞닿아 있습니다.

C++가 C를 확장하는 방식으로 태어났다는 사실은 오늘날까지 언어의 성격을 결정합니다. 대부분의 C 코드가 그대로 C++로 컴파일되고, C 라이브러리를 별도의 바인딩 없이 호출할 수 있습니다. 이 호환성은 C++가 빠르게 퍼진 이유였지만, 동시에 배열의 포인터 변환(decay), 암시적 형 변환, 초기화되지 않은 변수 같은 C의 위험한 부분까지 함께 물려받은 이유이기도 합니다. “모던 C++“의 많은 규칙은 이 유산을 피해 가는 방법에 가깝습니다.

표준화의 역사

timeline
    title C++ 표준화 타임라인
    1983 : C++ 이름 확정
    1998 : C++98 (첫 ISO 표준, STL 포함)
    2003 : C++03 (결함 수정)
    2011 : C++11 (모던 C++ 시작) : auto, 람다, 이동 의미론
    2014 : C++14 (C++11 보완)
    2017 : C++17 : structured bindings, std::optional
    2020 : C++20 (대규모 확장) : Concepts, Ranges, Coroutines, Modules
    2023 : C++23 : std::expected, std::mdspan
    2026 : C++26 : Reflection, Contracts

큰 전환점은 세 번 있었습니다. C++98은 표준 템플릿 라이브러리(STL)를 포함한 첫 국제 표준이었고, C++11은 이른바 “현대 C++“의 출발점이 되었으며, C++20은 Concepts, Ranges, Coroutines, Modules가 한꺼번에 들어간 C++11 이후 가장 큰 변화였습니다.

C++98과 C++11 사이에는 13년의 공백이 있었습니다(C++03은 결함 수정판이었습니다). 표준 위원회가 계획한 기능을 모두 완성한 뒤 표준을 내려다 일정이 계속 밀렸고, 그 사이 Java와 C#이 빠르게 성장했습니다. C++11 이후 위원회는 3년마다 준비된 기능만 싣고 출발하는 “기차 모델”로 바꿨고, 이것이 C++14, 17, 20, 23으로 이어지는 규칙적인 주기가 되었습니다.

실무에서 주의할 점은 “표준이 나왔다”와 “컴파일러가 지원한다”가 다르다는 것입니다. 예를 들어 C++20의 Modules는 표준 발표 후 몇 년이 지나서야 주요 컴파일러와 빌드 도구(CMake)에서 쓸 만해졌습니다. 프로젝트의 표준 버전을 정할 때는 팀이 쓰는 가장 오래된 컴파일러의 지원 현황을 먼저 확인해야 합니다.

”모던 C++“의 의미

C++11 이전의 코드는 다음과 같은 모습이었습니다.

// 구식 C++ (C++03)
std::vector<int>::iterator it = vec.begin();
for (; it != vec.end(); ++it) {
    std::cout << *it << '\n';
}

int* p = new int(42);
// ... 수동으로 delete 호출 필요
delete p;

C++11 이후에는 같은 일을 이렇게 씁니다.

// 현대 C++
auto it = vec.begin();  // 타입 추론
for (int x : vec) {     // 범위 기반 for
    std::cout << x << '\n';
}

auto p = std::make_unique<int>(42);  // 스마트 포인터 (std::make_unique는 C++14)
// 소유자가 범위를 벗어나면 자동으로 해제됨

“모던 C++“는 C++11 이후의 기능을 활용해 더 안전하고 간결하게 코드를 쓰는 스타일을 말합니다.

두 코드의 차이는 줄 수보다 실수할 수 있는 지점의 수에 있습니다. 구식 코드에서는 반복자 타입을 직접 적다가 const 컨테이너에서 iterator 대신 const_iterator가 필요해 컴파일 에러가 나기도 하고, new와 delete 사이에서 예외가 발생하거나 return이 추가되면 메모리가 새기 쉽습니다. std::make_unique로 만든 객체는 소유자가 범위를 벗어날 때 자동으로 해제되므로(RAII), 조기 반환이나 예외가 있어도 누수가 생기지 않습니다.

그래서 모던 C++를 배운다는 것은 새 문법을 외우는 것보다, 컴파일러와 타입 시스템이 실수를 막아 주는 방향으로 코드를 쓰는 습관을 들이는 것에 가깝습니다. 인터넷의 오래된 C++ 튜토리얼은 여전히 new/delete와 C 스타일 배열 위주로 가르치는 경우가 많으니, 학습 자료를 고를 때 C++11 이후를 기준으로 하는지 확인하는 것이 좋습니다.


왜 C++을 선택하는가

C++는 성능, 하드웨어 제어, 메모리 효율이 중요한 분야에서 선택됩니다. 게임에서는 가비지 컬렉션(GC) 일시정지가 없어 프레임 시간을 예측하기 쉽다는 점이, 고빈도 거래(HFT)에서는 마이크로초 단위의 지연 시간을 다룰 수 있다는 점이 중요합니다. 임베디드에서는 수십 KB 수준의 RAM과 플래시만 있는 마이크로컨트롤러에서도 동작할 수 있어야 하고, 운영체제·데이터베이스·브라우저 같은 시스템 소프트웨어에서는 메모리 배치와 자원 수명을 직접 통제해야 합니다.

정리하면, 밀리초나 마이크로초 단위의 지연이 중요하거나, 하드웨어를 직접 제어해야 하거나, 이미 큰 C++ 코드베이스가 있다면 C++가 적합합니다. 빠른 프로토타이핑이나 일반적인 웹 개발에는 Python, JavaScript, Go 같은 언어가 더 효율적입니다.

이 기준들의 공통분모는 “런타임 성능을 예측할 수 있어야 하는가”입니다. 가비지 컬렉터가 있는 언어에서는 GC 일시정지가 언제 일어날지 프로그래머가 완전히 통제할 수 없는데, 게임의 프레임 드랍이나 HFT의 지연 시간 급증은 바로 그 예측 불가능성 자체가 문제가 됩니다. C++가 메모리 관리를 프로그래머(또는 RAII 패턴)에게 맡기는 것은 불편함이 아니라, 이 예측 가능성을 확보하기 위한 설계 선택입니다.


C++이 사용되는 분야

  • 게임: Unreal Engine, Unity의 엔진 코어
  • 금융: 고빈도 거래(HFT) 시스템, 리스크 계산 엔진
  • 시스템: OS 구성 요소(Windows의 많은 부분, macOS의 드라이버 프레임워크 등), 데이터베이스(MySQL, MongoDB), 브라우저(Chrome, Firefox의 상당 부분)
  • 임베디드: 자동차(AUTOSAR Adaptive), 항공우주, 의료기기
  • 머신러닝: TensorFlow·PyTorch 백엔드(Python은 인터페이스이고, 실제 계산은 C++와 CUDA)

이 목록의 공통점은 오래 살아남는 대규모 코드베이스라는 것입니다. 브라우저나 데이터베이스는 수백만 줄 규모이고 수십 년 동안 유지보수되므로, “새 프로젝트에 어떤 언어가 좋은가”와는 별개로 C++를 읽고 고칠 수 있는 개발자가 계속 필요합니다. 한편 Linux 커널처럼 C로 작성된 시스템도 많고, Firefox와 Chrome은 새 구성 요소 일부를 Rust로 작성하기 시작했습니다. 그래서 시스템 분야에서 일하려면 C++ 하나만 아는 것보다 C와의 경계, 그리고 다른 언어와의 연동(FFI)까지 이해하는 편이 실제 업무에 가깝습니다.

머신러닝 분야의 C++는 조금 다른 모습입니다. 대부분의 개발자는 Python으로 모델을 만들고, C++는 연산 커널, 추론 엔진(ONNX Runtime, TensorRT), 모바일·엣지 배포처럼 성능과 배포 크기가 중요한 층에서 쓰입니다. 이 분야를 목표로 한다면 템플릿 메타프로그래밍보다 메모리 레이아웃, SIMD, GPU 프로그래밍 쪽 지식이 더 직접적으로 쓰입니다.


C++의 장단점

C++의 장점은 추상화를 써도 성능 손실이 거의 없다는 점(zero-overhead 추상화)과 SIMD 같은 하드웨어 기능까지 직접 쓸 수 있다는 점입니다. 절차적, 객체지향, 제네릭, 함수형 스타일을 한 언어 안에서 섞어 쓸 수 있고, Boost, Qt, OpenCV처럼 오랫동안 검증된 라이브러리 생태계도 큽니다.

단점은 배워야 할 개념(템플릿, 정의되지 않은 동작 등)이 많다는 것, 대형 프로젝트의 컴파일 시간이 길다는 것, 그리고 오래된 코드베이스에서는 수동 메모리 관리가 여전히 많이 남아 있다는 것입니다. 스마트 포인터, RAII, 새니타이저 같은 현대적인 도구부터 단계적으로 익히면 이 부담이 상당히 줄어듭니다.

초보자가 가장 먼저 부딪히는 어려움은 문법보다 에러 메시지와 빌드 환경입니다. 템플릿 코드에 타입 하나를 잘못 넘기면 수십 줄짜리 에러가 나오고, 헤더와 소스 파일, 링커의 역할을 모르면 undefined reference to ... 같은 링크 에러의 원인을 찾기 어렵습니다. 또 정의되지 않은 동작(UB)은 컴파일도 되고 실행도 되는데 가끔만 틀린 결과를 내기 때문에, 처음 배울 때부터 -Wall -Wextra 경고와 AddressSanitizer(-fsanitize=address)를 켜 두는 습관이 중요합니다. Python이나 Java를 먼저 배운 사람이라면 “왜 컴파일은 되는데 가끔만 죽을까?”라는 상황을 겪게 되는데, 초보자 코드에서 그 원인은 배열 범위 초과, 댕글링 참조(이미 사라진 객체를 가리키는 참조나 포인터), 초기화하지 않은 변수인 경우가 많습니다.

컴파일 시간 문제는 대형 프로젝트에서 실제로 생산성을 떨어뜨립니다. 헤더를 include하면 그 내용 전체가 그 헤더를 포함하는 모든 소스 파일에서 다시 컴파일되기 때문입니다. 전방 선언, PIMPL 관용구, ccache, 그리고 C++20 Modules가 이 문제를 줄이기 위한 도구들입니다.


현대 C++ (C++11 이후)

표준대표 기능
C++11auto, 람다, 스마트 포인터, 이동 의미론, 범위 기반 for, std::thread
C++14std::make_unique, 제네릭 람다, 반환 타입 추론
C++17structured bindings, std::optional, std::filesystem, if 초기화문
C++20Concepts, Ranges, Coroutines, Modules
C++23std::expected, std::mdspan, std::print
C++26정적 리플렉션, 계약(Contracts), std::execution(senders/receivers)

오랫동안 기대를 모은 패턴 매칭은 C++26에 들어가지 못했습니다.

처음 배울 때 이 기능을 모두 알 필요는 없습니다. 실무 코드에서 가장 자주 보는 것은 C++11의 auto, 람다, 스마트 포인터, 이동 의미론과 C++17의 structured bindings, std::optional 정도입니다. C++20의 Concepts와 Ranges는 코드를 읽을 때 알아볼 수 있을 정도로 익혀 두고, Coroutines와 Modules는 필요해질 때 배워도 늦지 않습니다. 새 기능을 쓸 때는 항상 팀의 컴파일러가 그 기능을 지원하는지 먼저 확인해야 합니다.


C++와 다른 언어 비교

C와 비교하면 C++는 RAII와 STL 덕분에 더 안전하고 생산적으로 코드를 쓸 수 있습니다. 다만 아주 작은 마이크로컨트롤러, 운영체제 커널, 안정적인 ABI가 필요한 라이브러리 인터페이스처럼 C가 여전히 기본인 영역도 있습니다.

Rust는 소유권 규칙으로 메모리 안전성을 컴파일 타임에 보장합니다. 새 시스템에서 Rust를 검토하는 팀이 늘고 있고, 기존 C++ 자산이 많은 곳은 C++를 유지하면서 일부 구성 요소만 Rust로 작성하는 경우가 많습니다.

Go는 단순한 언어와 고루틴 덕분에 개발과 동시성 처리가 쉽습니다. 일반적인 네트워크 서비스는 Go가 생산적이고, 지연 시간과 메모리 사용을 극단적으로 통제해야 하는 경우에는 C++가 유리합니다.

결국 언어 비교는 트레이드오프입니다. C++는 제어권을 많이 주는 대신 그만큼 책임도 프로그래머에게 넘깁니다. Rust는 컴파일러가 소유권 규칙을 강제해 메모리 버그를 막는 대신, 학습 초기에 컴파일러와 씨름하는 시간이 깁니다. Go는 GC와 단순한 언어로 생산성을 얻는 대신 세밀한 성능 제어를 포기합니다. “어느 언어가 더 좋은가”보다 “이 문제에서 무엇을 포기할 수 있는가”를 먼저 따지는 편이 좋은 선택으로 이어집니다.


학습 로드맵

초급 단계에서는 기본 문법, 포인터와 참조, 클래스, 그리고 컴파일과 링크 과정을 익힙니다. 중급 단계에서는 STL, 템플릿, 스마트 포인터, 이동 의미론, 멀티스레딩을 다루고, 고급 단계에서는 Concepts를 포함한 고급 템플릿, 메모리 모델, 성능 최적화, 설계 패턴으로 넘어갑니다.

로드맵에서 흔히 하는 실수는 초급 단계에서 포인터와 수동 메모리 관리에 너무 오래 머무르거나, 반대로 템플릿 메타프로그래밍 같은 고급 주제로 너무 빨리 넘어가는 것입니다. 포인터와 참조는 “무엇을 가리키는가, 누가 소유하는가”를 이해하는 데 필요하지만, 실제 코드에서는 std::vector, std::string, 스마트 포인터가 대부분의 메모리 관리를 대신합니다. 초급 단계의 목표는 STL 컨테이너로 작은 프로그램을 끝까지 만들어 보는 것이고, 그 과정에서 빌드 도구(CMake)와 디버거 사용법을 함께 익히는 것이 문법 하나를 더 아는 것보다 오래 쓸모가 있습니다.

참고 자료로는 이 블로그의 C++ 시리즈와 LearnCpp.com이 있습니다.


커리어 경로

  • 게임: 엔진·게임플레이·툴 프로그래머
  • 시스템: OS·드라이버·데이터베이스 개발자
  • 금융: HFT·퀀트 개발자
  • 임베디드: 펌웨어·RTOS·IoT 개발자

다음 단계

학습은 #1 환경 설정에서 시작해 C++ 시리즈 전체 목차 순서대로 진행하면 됩니다. 레퍼런스로는 cppreference.com을, 발표 영상으로는 CppCon 채널을, 책으로는 Scott Meyers의 *Effective Modern C++*를 추천합니다.