비야르네 스트롭스트룹이 어떻게 제로-코스트 추상화를 중심으로 C++을 설계했는지, 그리고 왜 제어권·툴링·생태계 때문에 성능 민감 소프트웨어에서 여전히 C++가 선택되는지 알아봅니다.

C++는 특정한 약속을 염두에 두고 만들어졌습니다: 클래스, 컨테이너, 제네릭 알고리즘처럼 표현력 있는 고수준 코드를 작성하되, 그 표현력에 대해 런타임에서 자동으로 추가 비용을 지불하지 않아야 한다는 것. 기능을 사용하지 않으면 비용을 지불하지 않아야 하고, 사용한다면 그 비용은 저수준 스타일로 직접 쓴 것과 거의 같아야 합니다.
이 글은 비야르네 스트롭스트룹(Bjarne Stroustrup)이 그 목표를 어떻게 언어로 구체화했는지, 그리고 왜 그 아이디어가 여전히 중요한지를 이야기합니다. 또한 성능에 신경 쓰는 누구나 C++가 무엇을 최적화하려 하는지—구호를 넘어서—이해하도록 돕는 실용적인 안내서이기도 합니다.
“고성능”은 단순히 벤치마크 수치를 높이는 것만이 아닙니다. 현실적으로 보통 다음 제약 중 적어도 하나가 실재한다는 뜻입니다:
이런 제약이 중요할 때, 숨겨진 오버헤드—불필요한 할당, 불필요한 복사, 필요 없는 가상 디스패치 등—는 “작동한다”와 “목표를 놓친다”의 차이를 만들 수 있습니다.
C++는 시스템 프로그래밍과 성능 민감 컴포넌트에서 흔한 선택입니다: 게임 엔진, 브라우저, 데이터베이스, 그래픽 파이프라인, 트레이딩 시스템, 로보틱스, 통신, 운영체제 일부 등. 유일한 선택은 아니며 많은 현대 제품은 여러 언어를 혼합합니다. 하지만 C++는 코드가 기계에 어떻게 매핑되는지 직접 제어해야 할 때 ‘핵심 루프’ 도구로 자주 남습니다.
다음으로 우리는 제로-코스트 아이디어를 쉬운 영어(여기서는 한국어)로 풀어내고, 그것을 RAII와 템플릿 같은 구체적 C++ 기법과 실제 팀이 마주하는 트레이드오프로 연결하겠습니다.
스트롭스트룹은 단지 “새 언어를 발명”하려 한 것이 아닙니다. 1970년대 말과 1980년대 초 그는 시스템 작업을 하며 C가 빠르고 기계에 가깝지만, 큰 프로그램은 조직하기 어렵고 변경하기 어렵고 쉽게 망가진다는 점을 경험했습니다.
그의 목표는 간단히 말하면 성취하기는 까다로웠습니다: 큰 프로그램을 구조화할 더 나은 방법—타입, 모듈, 캡슐화—을 도입하되, C가 제공하던 성능과 하드웨어 접근성을 포기하지 않는 것입니다.
초기 단계는 문자 그대로 **“C with Classes”**라고 불렸습니다. 이 이름은 방향을 암시합니다: 완전히 새로 설계한 언어가 아니라 진화. C가 이미 잘하던 것들(예측 가능한 성능, 직접 메모리 접근, 단순 호출 규약)을 유지하고, 큰 시스템을 구축하는 데 필요한 도구를 추가하는 방식이었습니다.
언어가 **C++**로 성숙해지면서 추가된 것들은 단순한 “기능 추가”가 아니었습니다. 잘 사용하면 고수준 코드가 손으로 쓴 C 스타일의 머신 코드와 같은 결과물로 컴파일되도록 설계되었습니다.
스트롭스트룹의 중심적 긴장은—지금도 그렇듯—다음 사이였습니다:
많은 언어는 세부를 숨기는 쪽을 택해 오버헤드를 숨깁니다. C++는 추상화를 구축하게 하되 "이것의 비용은 무엇인가?"라고 물어볼 수 있게 하고, 필요하면 저수준 작업으로 내려갈 수 있게 해줍니다.
그 동기—패널티 없는 추상화—는 C++의 초기 클래스 지원에서 RAII, 템플릿, STL 같은 이후의 아이디어까지 관통하는 실입니다.
“제로-코스트 추상화”는 슬로건처럼 들리지만, 사실은 트레이드오프에 관한 약속입니다. 일상적 버전은 이렇습니다:
사용하지 않으면 비용을 내지 않는다. 사용하면, 직접 저수준 코드를 썼을 때와 거의 같은 비용을 내야 한다.
성능 관점에서 “비용”은 런타임에서 프로그램이 추가로 수행해야 하는 모든 작업입니다. 예:
제로-코스트 추상화는 타입, 클래스, 함수, 제네릭 알고리즘 같은 깔끔한 고수준 코드를 작성하면서도 손으로 튜닝한 루프와 수동 자원 관리처럼 직접적인 머신 코드를 생성할 수 있게 해줍니다.
C++가 자동으로 모든 것을 빠르게 만들어 주지는 않습니다. 고수준 코드를 컴파일하면 효율적인 명령으로 줄어들 가능성을 제공할 뿐이며, 여전히 비용이 큰 패턴을 선택할 수 있습니다.
핫 루프에서 할당을 한다거나 큰 객체를 반복 복사한다거나, 캐시 친화적이지 않은 데이터 레이아웃을 선택하거나, 최적화를 막는 여러 수준의 간접화를 쌓으면 프로그램이 느려집니다. C++는 이를 막아주지 않습니다. “제로-코스트” 목표는 강제된 오버헤드를 피하는 것이지, 항상 좋은 결정을 보장하는 것은 아닙니다.
나머지 글에서는 이 아이디어를 구체화합니다. 컴파일러가 어떻게 추상화 오버헤드를 지우는지, RAII가 어떻게 더 안전하면서도 빠를 수 있는지, 템플릿이 손수 쓴 버전처럼 동작하는 이유, STL이 어떻게 숨겨진 런타임 작업 없이 재사용 가능한 빌딩 블록을 제공하는지(단, 주의해서 사용했을 때) 등을 살펴보겠습니다.
C++는 단순한 거래에 의존합니다: 빌드 타임에 더 많은 비용을 지불해 런타임에 덜 지불한다. 컴파일할 때 컴파일러는 단순히 코드를 번역하는 것을 넘어 런타임에서 발생할 오버헤드를 제거하려고 적극적으로 시도합니다.
컴파일러는 컴파일 중에 많은 비용을 "선불"로 지불할 수 있습니다:
목표는 깔끔하고 읽기 쉬운 구조가 손으로 직접 썼을 법한 머신 코드와 가깝게 변환되는 것입니다.
작은 헬퍼 함수 예:
int add_tax(int price) { return price * 108 / 100; }
종종 컴파일 후에는 함수 호출 자체가 사라집니다. 함수로 점프하고 인자를 설정한 뒤 반환하는 대신, 컴파일러가 그 산술을 함수를 호출한 자리로 그대로 붙여 넣을 수 있습니다. 즉 이름 붙은 함수라는 추상화가 사실상 없어지는 셈입니다.
루프도 최적화 대상입니다. 연속된 범위를 순회하는 단순 루프는 옵티마이저에 의해 변형될 수 있습니다: 불필요한 경계 검사 제거, 반복 불필요 계산의 루프 밖으로 이동(호이스트), 루프 바디 재구성 등으로 CPU를 더 효율적으로 사용하게 합니다.
이것이 제로-코스트 추상화의 실용적 의미입니다: 더 명확한 코드를 얻되, 당신이 사용한 구조에 대해 영구적인 런타임 비용을 지불하지 않습니다.
아무것도 공짜는 아닙니다. 더 많은 최적화와 더 많은 "사라지는 추상화"는 더 긴 컴파일 시간과 때로는 더 큰 바이너리 크기(예: 많은 호출 지점이 인라인될 때)를 의미할 수 있습니다. C++는 빌드 비용과 런타임 속도 사이의 균형을 선택할 수 있는 자유—그리고 책임—을 제공합니다.
RAII(Resource Acquisition Is Initialization)는 간단한 규칙이지만 큰 결과를 낳습니다: 자원의 수명은 스코프에 묶여 있다. 객체가 생성되면 자원을 획득하고, 객체가 스코프를 벗어나면 소멸자가 자동으로 자원을 해제합니다.
그 자원은 메모리, 파일, 뮤텍스 락, 데이터베이스 핸들, 소켓, GPU 버퍼 등 거의 모든 정리 대상이 될 수 있습니다. close(), unlock(), free()를 모든 경로에 걸쳐 기억할 필요 없이 정리를 소멸자 한 곳에 넣고 언어가 그것이 실행될 것을 보장하게 하세요.
수동 정리는 종종 "그림자 코드(shadow code)"를 키웁니다: 추가 if 검사, 중복된 return 처리, 모든 실패 지점 뒤의 정리 호출 등. 함수가 진화하면 분기를 하나 놓치기 쉽습니다.
RAII는 보통 직선형 코드를 만듭니다: 획득, 작업 수행, 스코프 종료가 정리를 처리하게 둠. 이는 누수(메모리), 이중 해제, 잠금 해제 누락 같은 버그와 방어적 관리로 인한 런타임 오버헤드를 줄입니다. 성능 관점에서, 핫 경로의 에러 처리 분기가 줄어들면 명령 캐시 동작과 분기 예측률이 좋아질 수 있습니다.
누수와 해제되지 않은 락은 단순한 정합성 문제를 넘은 성능 시한폭탄입니다. RAII는 자원 해제를 예측 가능하게 만들어 시스템이 부하에도 안정적으로 유지되도록 돕습니다.
RAII는 예외와 함께 할 때 특히 빛납니다. 스택 언와인딩 동안 소멸자가 호출되므로 제어 흐름이 예기치 않게 바뀌어도 자원이 해제됩니다. 예외는 도구이며, 비용은 사용 방식과 컴파일러/플랫폼 설정에 따라 달라집니다. 중요한 점은 RAII가 스코프를 벗어나는 방식에 관계없이 정리를 결정적으로 보장한다는 것입니다.
템플릿은 종종 "컴파일타임 코드 생성"으로 묘사되며, 이는 유용한 사고 모델입니다. 한 번 알고리즘(예: 아이템 정렬, 컨테이너에 아이템 저장)을 작성하면 컴파일러가 당신이 사용한 정확한 타입에 맞춘 버전을 생성합니다.
컴파일러가 구체 타입을 알기 때문에 함수 인라인, 적절한 연산 선택, 공격적인 최적화가 가능합니다. 많은 경우 이는 가상 호출, 런타임 타입 검사, 제네릭 코드를 작동시키기 위한 동적 디스패치를 피한다는 뜻입니다.
예를 들어 정수에 대한 템플릿화된 max(a, b)는 몇 개의 머신 명령으로 컴파일될 수 있습니다. 같은 템플릿이 작은 구조체와 함께 사용되어도 직접 비교와 이동으로 컴파일되어, 인터페이스 포인터나 런타임 타입 검사 없이 동작할 수 있습니다.
표준 라이브러리는 템플릿에 크게 의존합니다. 이는 숨겨진 작업 없이 재사용 가능한 빌딩 블록을 만들기 때문입니다:
std::vector\u003cT\u003e, std::array\u003cT, N\u003e)는 당신의 T를 직접 저장합니다.std::sort 같은 알고리즘은 비교 가능한 모든 데이터 타입에서 동작합니다.결과적으로, 컴파일 시점에 타입별로 특수화되므로 보통 손으로 쓴 특정 타입 버전처럼 성능이 납니다.
템플릿은 개발자에게 무료가 아닙니다. 컴파일 시간이 늘어나고(생성되고 최적화할 코드가 많아짐), 문제가 생겼을 때 에러 메시지가 길고 이해하기 어려울 수 있습니다. 팀들은 일반적으로 코딩 가이드라인, 좋은 툴링, 템플릿 복잡성을 투자 대비 이득이 나는 곳에 한정하는 방식으로 대응합니다.
표준 템플릿 라이브러리(STL)는 C++의 기본 도구상자로서 재사용 가능하면서도 촘촘한 머신 명령으로 컴파일될 수 있는 코드를 작성하도록 설계되었습니다. 별도 프레임워크가 아니라 표준 라이브러리의 일부이며, 제로-코스트 아이디어를 중심에 두고 설계되었습니다: 더 높은 수준의 빌딩 블록을 사용하되 요청하지 않은 작업에 대해 비용을 지불하지 않도록.
vector, string, array, map, unordered_map, list 등).sort, find, count, transform, accumulate 등).이 분리는 중요합니다. 각 컨테이너가 sort나 find를 재발명하는 대신, STL은 잘 테스트된 단일 알고리즘 집합을 제공하며 컴파일러는 이를 적극적으로 최적화할 수 있습니다.
STL 코드는 많은 결정이 컴파일 타임에 내려지기 때문에 빠를 수 있습니다. std::vector\u003cint\u003e를 정렬하면 컴파일러는 요소 타입과 이터레이터 타입을 알고 비교를 인라인하고 루프를 최적화해 수작업 버전과 유사한 성능을 냅니다. 핵심은 액세스 패턴에 맞는 데이터 구조를 선택하는 것입니다.
vector vs list: vector가 기본 선택인 경우가 많습니다. 요소가 연속 메모리에 있어 반복과 임의 접근에 캐시 친화적이고 빠릅니다. list는 반복자가 안정적이어야 하고 이동 없이 중간 삽입/스플라이스가 많을 때만 고려하세요—노드당 오버헤드가 있고 순회가 느릴 수 있습니다.
unordered_map vs map: unordered_map은 보통 평균적인 키 조회에 빠릅니다. map은 키를 정렬해 범위 질의(e.g., "A와 B 사이의 모든 키")와 예측 가능한 반복 순서를 제공하지만, 조회는 일반적으로 좋은 해시 테이블보다 느립니다.
자세한 컨테이너 결정 가이드는 /blog/choosing-cpp-containers를 참고하세요.
모던 C++는 스트롭스트룹의 원래 아이디어인 "패널티 없는 추상화"를 버리지 않았습니다. 대신 새 기능들은 더 명확한 코드를 쓰면서도 컴파일러가 촘촘한 머신 코드를 만들어낼 기회를 제공하는 데 집중합니다.
느려지는 일반적 원인 중 하나는 불필요한 복사—큰 문자열, 버퍼, 데이터 구조를 전달하기 위해 반복해서 복사하는 것—입니다.
이동 의미론은 "단순히 넘겨주는 것이라면 복사하지 말자"는 간단한 아이디어입니다. 객체가 임시이거나 더 이상 사용하지 않는다면 내부 자원을 새 소유자에게 옮기는 것으로 복제를 피할 수 있습니다. 일상적인 코드에서는 더 적은 할당, 적은 메모리 트래픽, 더 빠른 실행을 의미하는 경우가 많습니다—바이트 단위로 수동 관리할 필요 없이.
constexpr: 런타임에서 덜 하도록 더 일찍 계산어떤 값과 결정은 변하지 않습니다(테이블 크기, 구성 상수, 조회 테이블 등). constexpr를 사용하면 컴파일 시점에 특정 결과를 계산하게 해 런타임에서의 작업을 줄일 수 있습니다.
이득은 속도와 단순성 모두에 있습니다: 코드는 평범한 계산처럼 읽히되 결과는 상수로 "베이크"될 수 있습니다.
Ranges(및 뷰 같은 관련 기능)는 "이 항목들을 가져와서 필터하고, 변환하라"는 것을 가독성 있게 표현하게 해줍니다. 잘 사용하면 이들은 강제된 런타임 레이어 없이 단순한 루프로 컴파일됩니다.
이들 기능은 제로-코스트 방향을 지원하지만, 성능은 여전히 사용 방식과 컴파일러의 최적화 능력에 달려 있습니다. 깔끔한 고수준 코드는 종종 아름답게 최적화되지만, 실제로 속도가 중요할 때는 항상 측정하는 것이 중요합니다.
C++는 고수준 코드를 매우 빠른 머신 명령으로 컴파일할 수 있지만, 기본적으로 빠른 결과를 보장하지는 않습니다. 성능이 떨어지는 이유는 종종 템플릿이나 깔끔한 추상화를 썼기 때문이 아니라, 작은 비용들이 핫 경로에서 수백만 번 곱해지기 때문입니다.
자주 나타나는 패턴들:
이들은 C++ 고유의 문제라기보다 설계와 사용의 문제입니다. 차이점은 C++가 이를 고칠 수 있는 충분한 제어권을 주고, 실수할 여지도 충분히 준다는 점입니다.
비용 모델을 단순하게 유지하는 습관으로 시작하세요:
reserve()), 내부 루프에서 임시 컨테이너 생성 회피.어디에 시간이 쓰이는가? 할당이 얼마나 발생하는가? 어떤 함수가 가장 많이 호출되는가? 같은 기본 질문에 답할 수 있는 프로파일러를 쓰세요. 관심 부분에 대한 경량 벤치마크를 병행하면 좋습니다.
이 과정을 일관되게 하면 “제로-코스트 추상화”는 실용적이 됩니다: 가독성 있는 코드를 유지한 뒤 측정으로 드러난 특정 비용만 제거하면 됩니다.
밀리초(또는 마이크로초)가 단지 "있으면 좋은" 것이 아니라 제품 요구사항인 곳에서 C++는 계속 사용됩니다. 낮은 레이턴시 트레이딩 시스템, 게임 엔진, 브라우저 컴포넌트, 데이터베이스 및 스토리지 엔진, 임베디드 펌웨어, 고성능 컴퓨팅(HPC) 워크로드에서 흔히 볼 수 있습니다. 이들이 유일한 사례는 아니지만, 언어가 계속 존재하는 이유를 잘 보여줍니다.
많은 성능 민감 도메인은 평균 처리량보다 예측 가능성—프레임 드롭, 오디오 글리치, 놓친 시장 기회, 실시간 마감 누락을 일으키는 꼬리 지연—을 더 신경 씁니다. C++는 메모리 할당 시점, 해제 시점, 데이터 레이아웃을 팀이 직접 결정하게 해 줍니다—이 선택들이 캐시 동작과 지연 시간 스파이크에 큰 영향을 줍니다.
추상화가 직관적인 머신 코드로 컴파일될 수 있으므로, 유지보수성을 위해 구조화하되 그 구조에 대해 자동으로 런타임 오버헤드를 지불하지 않는 방식으로 코드를 구성할 수 있습니다. 비용이 발생할 때(동적 할당, 가상 디스패치, 동기화) 그 비용은 보통 가시적이고 측정 가능합니다.
실용적인 이유로 C++가 여전히 흔한 이유 중 하나는 상호 운용성입니다. 많은 조직이 수십 년의 C 라이브러리, 운영체제 인터페이스, 디바이스 SDK, 검증된 코드를 가지고 있어 전부 다시 쓰기 어렵습니다. C++는 C API를 직접 호출할 수 있고, 필요할 때 C 호환 인터페이스를 노출하며, 코드베이스의 일부를 한꺼번에 다시 작성하지 않고 점진적으로 현대화할 수 있게 해줍니다.
시스템 프로그래밍과 임베디드 작업에서는 "메탈에 가까움"이 여전히 중요합니다: 명령어 수준 접근, SIMD, 메모리 매핑 I/O, 플랫폼 특화 최적화. 성숙한 컴파일러와 프로파일링 도구와 결합해 C++는 바이너리, 의존성, 런타임 동작을 제어하면서 성능을 끌어내야 할 때 자주 선택됩니다.
C++는 매우 빠르고 유연할 수 있기 때문에 충성도를 얻었습니다—하지만 그 힘에는 대가가 따릅니다. 사람들의 비판은 근거 없는 것이 아닙니다: 언어가 크고 오래되었으며, 오래된 코드베이스는 위험한 관행을 포함하고 있고, 실수는 충돌, 데이터 손상, 보안 문제로 이어질 수 있습니다.
C++는 수십 년에 걸쳐 성장해왔고, 그 흔적이 있습니다. 같은 일을 하는 여러 방법과 작은 실수가 큰 문제로 이어지는 "날카로운 모서리"가 존재합니다. 자주 나오는 두 가지 문제:
레거시 코드에는 원시 new/delete, 수동 메모리 소유권 관리, 검사 없는 포인터 산술이 여전히 흔합니다.
모던 C++ 실천은 이익을 얻으면서도 발사 가능한 총(foot-guns)을 피하는 것입니다. 팀들은 지침과 더 안전한 하위집합을 채택해 실패 모드를 줄입니다—완전한 안전 보장이 아니라 실용적 위험 감소를 목표로.
일반적인 조치들:
std::vector, std::string)를 수동 할당보다 선호.std::unique_ptr, std::shared_ptr) 사용.clang-tidy 규칙을 적용.표준은 더 안전하고 명확한 코드로 나아가고 있습니다: 더 나은 라이브러리, 표현력이 높은 타입, 계약(contracts), 안전 가이드라인과 툴 지원 개선 작업이 계속됩니다. 트레이드오프는 여전합니다: C++는 지렛대를 제공하지만 팀은 규율, 리뷰, 테스트, 현대적 관행으로 신뢰성을 벌어야 합니다.
C++는 성능과 자원에 대한 세밀한 제어가 필요하고 그에 투자할 수 있을 때 좋은 선택입니다. "C++가 더 빠르다"가 아니라 "C++는 어떤 작업이 언제, 어떤 비용으로 일어나는지를 당신이 결정하게 해준다"는 점이 핵심입니다.
다음 항목 대부분이 해당되면 C++를 선택하세요:
다른 언어를 고려할 때:
C++를 선택하면 초기에 가드레일을 설정하세요:
new/delete 회피, std::unique_ptr/std::shared_ptr는 의도적으로 사용, 체크 없는 포인터 산술 금지.평가나 마이그레이션을 계획한다면 내부 의사결정 노트를 남기고 /blog 같은 팀 공간에 공유하는 것이 신입과 이해관계자에게 도움이 됩니다.
성능 민감한 핵심이 C++에 남아 있더라도 많은 팀은 주변 제품 코드(대시보드, 관리자 도구, 내부 API, 요구사항을 검증하는 프로토타입)를 빠르게 배포해야 합니다.
바로 그곳에서 Koder.ai가 실용적인 보완 역할을 합니다. 채팅 인터페이스로 웹, 서버, 모바일 애플리케이션을 구축하게 해주는 바이브 코딩 플랫폼으로(웹은 React, 백엔드는 Go + PostgreSQL, 모바일은 Flutter), 기획 모드, 소스 코드 내보내기, 배포/호스팅, 맞춤 도메인, 롤백 가능한 스냅샷 등의 옵션을 제공합니다. 다시 말해: 당신은 "핫 패스" 주변의 모든 것을 빠르게 반복하면서 C++ 컴포넌트는 제로-코스트 추상화와 촘촘한 제어가 중요한 부분에 집중하도록 할 수 있습니다.
“제로-코스트 추상화”는 설계 목표를 나타냅니다: 기능을 사용하지 않으면 런타임 오버헤드를 부담하지 않아야 하고, 기능을 사용하면 생성된 머신 코드가 저수준 스타일로 직접 작성했을 때와 거의 같아야 한다는 뜻입니다.
실용적으로 말하면, 타입, 함수, 제네릭 알고리즘 같은 더 명확한 코드를 작성해도 자동으로 추가적인 할당, 간접 참조, 또는 디스패치 비용을 내지 않는다는 의미입니다.
여기서 말하는 “비용”은 실행 시간에 추가로 발생하는 작업을 뜻합니다. 예를 들어:
목표는 이러한 비용을 가시화하고 모든 프로그램에 강제로 부담시키지 않는 것입니다.
컴파일러가 추상화를 컴파일 시점에 볼 수 있을 때 가장 잘 작동합니다—작은 함수의 인라인, constexpr 같은 컴파일타임 상수, 구체 타입으로 인스턴스화된 템플릿 등이 전형적인 사례입니다.
반면 런타임 간접 호출이 지배적이거나(예: 핫 루프에서 많은 가상 디스패치), 잦은 할당과 포인터 추적 구조를 도입하면 효과가 떨어집니다.
C++는 많은 비용을 빌드 타임에 전가시켜 런타임을 가볍게 만듭니다. 대표적 예:
이 혜택을 얻으려면 최적화 옵션(예: -O2/-O3)으로 컴파일하고, 컴파일러가 추론하기 쉬운 구조로 코드를 작성하세요.
RAII는 스코프에 자원 수명을 묶는 원칙입니다: 생성자에서 자원을 획득하고, 소멸자에서 해제합니다. 메모리, 파일, 락, 소켓, GPU 버퍼 등 대부분의 정리 작업에 사용합니다.
실용적 습관:
std::vector, std::string)을 선호하세요.RAII는 예외와 특히 잘 어울립니다. 스택 언와인딩 동안에도 소멸자가 호출되어 자원이 해제되기 때문에 제어 흐름이 예기치 않게 바뀌어도 정리가 보장됩니다.
성능 측면에서, 예외는 ‘던져질 때’ 비용이 크고, 단순히 가능성만 있는 경우에는 비용이 적습니다. 핫 경로에서 예외가 자주 발생한다면 오류 코드나 expected-유사 타입으로 설계하는 것이 낫습니다. 정말 예외적인 상황에서만 던져진다면 RAII와 예외 조합은 빠른 경로를 단순하게 유지해 줍니다.
템플릿은 제네릭 코드를 컴파일 타임에 타입별로 특수화해 주므로 인라인을 가능하게 하고 런타임 타입 검사나 디스패치를 피하게 합니다.
계획해야 할 트레이드오프:
템플릿 복잡성은 핵심 알고리즘이나 재사용 가능 컴포넌트에 한정해 사용하는 것이 보통 이득입니다.
연속 메모리 저장(std::vector)이 반복 및 임의 접근에 캐시 친화적이고 빠릅니다. std::list는 반복자가 안정적이어야 하거나 중간 삽입/스플라이스가 많아 요소 이동을 피해야 할 때 유리하지만, 노드당 오버헤드와 순회 비용이 큽니다.
맵 선택:
std::unordered_map: 평균적으로 빠른 키 조회std::map: 키가 정렬돼 있어 범위 질의와 예측 가능한 반복 순서를 제공하지만 조회는 보통 해시 테이블보다 느립니다.심층 가이드는 /blog/choosing-cpp-containers를 참조하세요.
핫 경로에서 비용이 곱해져 나가는 패턴들:
이들은 C++만의 문제는 아니며 설계와 사용 방식의 문제입니다. 프로파일링으로 우선순위를 정해 해결하세요.
성능과 안전을 잃지 않고 C++를 쓰기 위한 실무적 관행:
new/delete 회피std::unique_ptr / std::shared_ptr)하고 의도적으로 사용clang-tidy 같은 규칙 적용이렇게 하면 제어권을 유지하면서도 정의되지 않은 동작과 깜짝 오버헤드를 줄일 수 있습니다.