[면접 준비] C++ 항목 정리
✅ 1. 기본 문법 및 언어 구조
🔷포인터와 참조의 차이는 무엇인가?
🔹 모범 답변
- 포인터는 주소값을 저장하며 *로 접근합니다.
- 참조는 기존 변수의 별칭으로 동작하며, null이나 재바인딩이 불가능합니다.
- 포인터는 동적 할당과 배열 탐색에 유리하고, 참조는 함수 인자 전달 등에 많이 사용됩니다.
🔸 심화 질문
- 참조로 구현할 수 없는 상황은?
- 참조로 void* 전달 가능한가?
- T*&와 같은 참조형 포인터는 어떤 용도로 쓰이나요?
✅ 심화 답변
- 참조는 null이나 재할당이 불가능하므로 동적 재연결이 필요한 상황은 포인터를 써야 함
- void*는 타입 정보가 없으므로 참조로 전달 불가
- T*&는 포인터 자체를 참조하므로, 호출자에게 포인터를 변경해서 전달할 수 있음
🔷const 키워드의 용도와 위치에 따른 의미 차이를 설명해주세요.
|
cs |
|---|
🔹 모범 답변
- const int* p: 값은 고정, 포인터는 변경 가능
- int* const p: 포인터는 고정, 값은 변경 가능
- const는 함수 매개변수, 리턴 타입, 클래스 멤버 함수 등 여러 위치에서 사용됨
🔸 심화 질문
- const 멤버 함수와 mutable 멤버 변수의 관계는?
- const_cast는 언제 사용되며 위험성은?
✅ 심화 답변
- const 멤버 함수는 멤버를 수정하지 못하지만, mutable 변수는 예외
- const_cast는 const 속성을 강제로 제거하지만, 진짜 const 객체에서 쓰면 undefined behavior 발생 가능
🔷 static 키워드는 어디에 사용할 수 있고 각각 어떤 의미인가요?
🔹 모범 답변
- 함수 내 static: 정적 지역 변수
- 클래스 내 static: 인스턴스 없이 공유
- 전역에서 static: 내부 연결 (translation unit 제한)
🔸 심화 질문
- static 변수의 초기화 순서 문제란?
- static 멤버와 싱글턴과의 차이?
✅ 심화 답변
- 서로 다른 static 객체 간의 초기화 순서가 undefined일 수 있음 (static initialization order fiasco)
- static 멤버는 단일 값 공유, 싱글턴은 객체 자체를 관리함 (보통 캡슐화와 lazy init 포함)
🔷 C++에서 함수 오버로딩과 오버라이딩의 차이는 무엇인가요?
🔹 모범 답변
- 오버로딩은 같은 이름의 함수가 매개변수 시그니처만 다른 경우
- 오버라이딩은 상속 관계에서 virtual 함수를 재정의하는 것
- 오버라이딩은 런타임 다형성, 오버로딩은 컴파일 타임 분기
🔸 심화 질문
- 함수 오버로딩이 모호해지는 경우는?
- 오버라이딩에서 override, final의 역할은?
✅ 심화 답변
- 암시적 변환이 가능한 경우 오버로딩 충돌 발생 가능
- override는 오타 방지, final은 하위 클래스에서 오버라이딩 방지
🔷 객체 생성 시 생성자 호출 순서는 어떻게 되나요? (상속 포함)
🔹 모범 답변
- 기본 → 멤버 → 파생 클래스 순
- 파생 클래스 생성 시, 기반 클래스가 먼저 호출됨
- 멤버 변수는 선언 순서대로 생성됨
🔸 심화 질문
- 상속된 클래스에 가상 함수가 있다면 생성자에서 호출 가능한가?
- 다중 상속 시 생성 순서는?
✅ 심화 답변
- 생성자 내에서는 해당 클래스의 vtable이 완성되지 않아 가상 함수 호출 위험
- 다중 상속은 선언 순서에 따라 부모 생성자 호출, 가상 상속이 있는 경우 virtual base는 한 번만 호출됨
🔷 C++에서 컴파일과 링크 과정은 어떻게 이루어지나요?
🔹 모범 답변
- Preprocessing (전처리)
- Compilation (각 소스 파일을 obj로 컴파일)
- Linking (외부 참조 결합)
- Executable 생성
- 컴파일 타임 오류는 각 파일 내부에서 발생, 링크 오류는 외부 참조 불일치 시 발생
🔸 심화 질문
- ODR(One Definition Rule)이란?
- inline 함수는 여러 번 정의될 수 있는데 ODR을 위반하지 않는 이유는?
✅ 심화 답변
- ODR: 동일한 기호는 한 번만 정의되어야 한다는 규칙
- inline 함수는 동일한 내용이라면 컴파일러가 허용 (기계어로 같다면 ok)
🔷가상 함수와 다형성
🔹 모범 답안:
가상 함수는 virtual 키워드로 선언되어 파생 클래스에서 재정의 가능하며, 런타임에 바인딩됩니다. 이를 통해 객체를 기반으로 동작이 결정되는 다형성이 구현됩니다.
|
1
2
3
4
5
6
7
8
9
|
class Base
{
public: virtual void Print() { std::cout << "Base\n"; }
};
class Derived : public Base
{
public: void Print() override { std::cout << "Derived\n"; }
};
|
cs |
- 포인터나 참조를 통해 Base* 형으로 접근해도 실제 객체의 Print()가 호출됨
- vtable을 통해 동작하며, 최소 1개의 가상 함수가 있으면 vptr이 생성됨
🔸 심화 질문:
- virtual 소멸자를 선언하지 않았을 때 발생할 수 있는 문제는?
- vtable은 클래스마다 하나인가 객체마다 하나인가?
- final, override 키워드의 의미와 쓰임은?
✅ 2. 메모리 및 자원 관리
🔷 new와 malloc의 차이점은 무엇인가요?
🔹 모범 답변
- new는 C++ 연산자이며, 생성자 호출과 타입 안전한 메모리 할당을 동시에 수행합니다.
- malloc은 C 함수이며, 단순한 메모리 블록을 할당하며 생성자 호출이 이루어지지 않습니다.
- new는 실패 시 예외를 throw, malloc은 nullptr을 반환합니다.
🔸 심화 질문
- new와 malloc을 혼용할 경우 어떤 문제가 생기나요?
- operator new와 new 연산자의 차이는?
✅ 심화 답변
- new로 할당 후 free() 호출 시 소멸자가 호출되지 않아 리소스 누수
- operator new는 메모리 할당 함수(함수 호출), new는 객체 생성까지 포함한 연산자
🔷 스마트 포인터(unique_ptr, shared_ptr, weak_ptr)의 동작 방식과 용도
🔹 모범 답변
- unique_ptr: 소유권이 하나, 이동 가능, 복사 불가
- shared_ptr: 참조 카운트 기반 공유, 마지막 하나가 파괴되면 자원 해제
- weak_ptr: shared_ptr의 순환 참조 방지용, 참조 카운트에 영향을 주지 않음
🔸 심화 질문
- shared_ptr의 참조 카운트는 어디 저장되며 언제 해제되나요?
- weak_ptr을 사용할 때 expired()와 lock()의 차이는?
✅ 심화 답변
- 별도의 컨트롤 블록에 참조 카운트와 weak 카운트가 함께 저장됨
- lock()은 유효할 때만 shared_ptr 반환, expired()는 만료 여부 확인
🔷 RAII란 무엇이며, 왜 중요한가요?
🔹 모범 답변
- RAII(Resource Acquisition Is Initialization): 자원은 객체 생성과 동시에 확보되고, 소멸 시 자동 해제됨
- 예: std::lock_guard, std::unique_ptr, fstream 등
- 예외 안전성과 자원 누수 방지에 매우 효과적
🔸 심화 질문
- RAII가 멀티스레딩에서 중요한 이유는?
- RAII를 위반했을 때 발생할 수 있는 상황은?
✅ 심화 답변
- lock을 획득한 후 예외 발생 시 unlock이 자동으로 되지 않으면 데드락 발생 → RAII로 방지 가능
- 수동 delete나 unlock은 누락되기 쉬워 자원 누수나 교착 상태 초래 가능
🔷 shallow copy와 deep copy의 차이는 무엇인가요?
🔹 모범 답변
- shallow copy는 단순히 포인터 값만 복사
- deep copy는 포인터가 가리키는 대상도 새로 복사
- 포인터 멤버가 있다면 deep copy를 구현해 이중 해제(double free) 방지 필요
🔸 심화 질문
- Rule of Three/Five와 deep copy의 관계는?
- move constructor는 shallow copy일까요?
✅ 심화 답변
- deep copy가 필요한 경우, 복사 생성자/대입 연산자/소멸자 모두 정의해야 하므로 Rule of Three가 성립됨
- move는 자원 이동으로 shallow와 다르며, 기존 객체의 자원을 빼앗는 개념 (소유권 이전)
🔷 메모리 누수(memory leak)는 어떻게 탐지하고 방지할 수 있나요?
🔹 모범 답변
- delete 누락, 스마트 포인터 미사용, 예외 시 경로 누락 등에서 발생
- 해결법: RAII, 스마트 포인터, valgrind, CRT Debug Heap, AddressSanitizer
🔸 심화 질문
- STL 컨테이너에서 메모리 누수가 발생할 수 있나요?
- 메모리 누수와 dangling pointer의 차이는?
✅ 심화 답변
- vector<T*>에서 내부 포인터를 delete하지 않으면 누수 발생
- memory leak은 해제 안된 메모리, dangling pointer는 해제된 후 접근한 포인터
✅ 3. 객체 지향 (OOP)
🔷 다형성이란 무엇이며, C++에서 어떻게 구현하나요?
🔹 모범 답변
다형성은 동일한 인터페이스를 통해 다양한 동작을 수행할 수 있는 성질입니다.
C++에서는 virtual 키워드를 이용한 가상 함수와 상속을 통해 런타임 다형성을 구현할 수 있습니다.
🔸 심화 질문
- 정적 다형성과 동적 다형성의 차이는?
- C++에서 다형성을 컴파일 타임에 구현하는 방법은?
✅ 심화 답변
- 정적 다형성은 템플릿이나 함수 오버로딩을 통한 컴파일 타임 분기, 동적은 가상 함수 기반 런타임 분기
- 정적 다형성은 인라인화가 가능해 성능상 유리함, 예: CRTP(Curiously Recurring Template Pattern)
🔷 가상 함수 테이블(vtable)의 구조와 작동 원리는?
🔹 모범 답변
- 가상 함수가 있는 클래스는 컴파일러가 **vtable(가상 함수 테이블)**을 생성합니다.
- 객체는 내부에 **vptr(가상 함수 테이블 포인터)**를 포함하며, 이 포인터를 통해 실제 구현을 런타임에 호출합니다.
- 파생 클래스가 함수를 오버라이딩하면, 해당 슬롯의 함수 포인터가 대체됩니다.
🔸 심화 질문
- 생성자/소멸자에서 가상 함수를 호출하면 어떤 문제가 생기나요?
- 다중 상속 시 vtable은 어떻게 관리되나요?
✅ 심화 답변
- 생성자/소멸자에서는 아직 vptr이 파생 클래스용으로 설정되지 않기 때문에, 기반 클래스의 함수만 호출됨
- 다중 상속 시 각 부모 클래스별로 별도의 vptr/vtable이 존재할 수 있음 (컴파일러에 따라 다름)
🔷 순수 가상 함수와 추상 클래스란?
🔹 모범 답변
- 순수 가상 함수는 구현이 없고 = 0으로 선언되며, 자식 클래스가 반드시 재정의해야 합니다.
- 순수 가상 함수를 1개 이상 포함한 클래스는 추상 클래스로 간주되며, 객체를 직접 생성할 수 없습니다.
🔸 심화 질문
- 추상 클래스에 생성자/소멸자를 정의할 수 있을까요?
- 인터페이스로서 추상 클래스를 설계할 때 주의할 점은?
✅ 심화 답변
- 생성자/소멸자 정의 가능하며, 보통 소멸자는 virtual로 선언해 다형성 소멸 보장
- 다중 상속을 고려할 경우 인터페이스는 상태(state)를 가지지 않도록 구성하는 것이 좋음
🔷 상속 시 생성자/소멸자 호출 순서는?
🔹 모범 답변
- 생성자: 기반 클래스 → 멤버 변수 → 파생 클래스 순으로 호출
- 소멸자: 파생 클래스 → 멤버 변수 → 기반 클래스 순으로 역순 호출
- 가상 소멸자가 없으면 파생 클래스의 소멸자가 호출되지 않아 자원 누수 발생 가능
🔸 심화 질문
- 다형성을 사용하는 클래스는 항상 virtual 소멸자가 필요할까요?
- 멤버 초기화 리스트와 생성자 본문의 차이는?
✅ 심화 답변
- 가상 함수가 있는 클래스는 virtual ~Class()를 반드시 선언해야 하며, 그렇지 않으면 delete 시 누락 위험
- 멤버 초기화 리스트는 생성자 호출 전에 실행, 본문은 그 이후 → 초기화가 아닌 대입이 될 수 있어 비효율적
🔷 다중 상속의 문제점과 해결 방법은?
🔹 모범 답변
- 이름 충돌(name hiding), 모호성(ambiguity), 복잡한 메모리 레이아웃 등이 문제
- 공통 기반 클래스가 중복 상속될 경우 diamond 문제(다이아몬드 상속) 발생
- 해결책: virtual 키워드를 사용하여 가상 상속(virtual inheritance) 적용
🔸 심화 질문
- 가상 상속 시 메모리 배치에 어떤 변화가 생기나요?
- 다중 상속을 피하기 위해 어떤 설계 방식이 권장되나요?
✅ 심화 답변
- 가상 상속에서는 vtable뿐 아니라, 공통 기반 클래스의 오프셋 정보를 추적해야 하므로 메모리 배치가 더 복잡해짐
- **구성(composition)**이나 인터페이스 기반 상속을 통해 다중 상속을 피하는 것이 유지보수와 안정성 측면에서 유리
✅ 4. 템플릿 & 제네릭 프로그래밍
🔷 함수 템플릿과 클래스 템플릿의 차이점은?
🔹 모범 답변
- 함수 템플릿은 함수 호출 시 타입을 자동 추론하여 템플릿 인스턴스 생성
- 클래스 템플릿은 인스턴스를 생성할 때 타입을 명시적으로 지정해야 함
- 함수 템플릿은 코드 재사용에 유리하며 STL의 핵심 구현 방식임
🔸 심화 질문
- 함수 템플릿의 오버로딩과 특수화는 어떻게 다르나요?
- 클래스 템플릿의 static 멤버는 타입별로 몇 개 생성되나요?
✅ 심화 답변
- 오버로딩은 시그니처에 따른 분기, 특수화는 특정 타입 전용 구현
- 클래스 템플릿의 static 멤버는 타입마다 각각 별도로 존재
🔷 템플릿 인스턴스화 시점은 언제인가요?
🔹 모범 답변
템플릿은 정의가 필요한 시점에 인스턴스화됩니다.
즉, 실제 사용 시점에서 컴파일러가 타입을 바탕으로 코드를 생성합니다.
🔸 심화 질문
- 선언만 해놓고 정의하지 않은 템플릿을 사용할 수 있나요?
- 템플릿 정의는 헤더에 두어야 하는 이유는?
✅ 심화 답변
- 정의가 없다면 링크 오류 발생
- 컴파일러가 인스턴스화를 위해 템플릿 정의가 필요하므로 헤더에 정의를 포함해야 함
🔷 SFINAE와 enable_if는 어떤 상황에서 쓰이나요?
🔹 모범 답변
SFINAE(Substitution Failure Is Not An Error)는 템플릿 인자 대체 실패를 오류로 처리하지 않고 다른 오버로드로 대체하는 메커니즘입니다.
std::enable_if를 통해 특정 조건에 따라 함수/클래스 템플릿의 활성화를 제어할 수 있습니다.
🔸 심화 질문
- SFINAE가 필요한 이유는?
- C++20에서는 어떤 문법이 SFINAE를 대체하나요?
✅ 심화 답변
- 여러 함수 중 적합한 템플릿만 활성화시켜 컴파일 오류 방지 및 유연성 확보
- C++20에서는 requires, concept을 통해 코드 가독성과 타입 제약 표현이 훨씬 명확해짐
🔷 C++20의 concept는 무엇인가요?
🔹 모범 답변
concept는 템플릿 인자의 타입 요구조건을 명확하게 표현하는 문법입니다.
기존 SFINAE보다 가독성 좋고 에러 메시지가 간결합니다.
🔸 심화 질문
- concept의 내부 동작은 SFINAE와 동일한가요?
- concept와 requires 절의 차이는?
✅ 심화 답변
- concept도 내부적으로는 SFINAE와 유사하게 작동하지만 문법 수준에서 직접 지원
- concept는 템플릿 정의 외부/내부에서 쓸 수 있고, requires는 조건을 정의하는 표현식
✅ 5. STL (표준 템플릿 라이브러리)
✅ STL 컨테이너 비교 - 핵심 개념 요약
컨테이너 내부 구조 검색 속도 삽입/삭제 정렬 유지 사용 예
| vector | 동적 배열 | O(n) | 끝: O(1), 중간: O(n) | ❌ | 순차적 처리, 캐시 효율 |
|---|---|---|---|---|---|
| list | 이중 연결 리스트 | O(n) | O(1) (노드 위치 알고 있을 때) | ❌ | 빈번한 삽입/삭제 |
| deque | 분할 배열 | O(n) | 앞뒤 O(1) | ❌ | double-ended queue |
| set / map | Red-Black Tree | O(log n) | O(log n) | ✅ | 정렬 필요 시 |
| unordered_set / unordered_map | Hash Table | 평균 O(1), 최악 O(n) | O(1) | ❌ | 빠른 탐색 목적 |
🔷 vector vs list vs deque의 내부 구조 및 특징은?
🔹 모범 답변
- vector: 연속 메모리, 빠른 접근, 캐시 효율 우수
- list: 이중 연결 리스트, 삽입/삭제 빠름, 캐시 비효율
- deque: 분할 배열 구조, 앞뒤 삽입 빠름, 중간 삽입/삭제 비효율
🔸 심화 질문
- vector의 capacity 관리 방식은?
- deque의 내부 블록 구조는?
✅ 심화 답변
- vector는 2배 방식으로 capacity를 재할당하며 재할당 시 모든 요소 복사 발생
- deque는 고정 크기 블록 배열을 포인터 배열로 관리해 앞뒤 삽입을 빠르게 처리
🔷 map과 unordered_map의 차이점은?
🔹 모범 답변
- map: Red-Black Tree 기반, O(log n), 정렬된 순서
- unordered_map: 해시 기반, 평균 O(1), 순서 없음
🔸 심화 질문
- unordered_map에서 충돌이 많이 발생하면?
- 커스텀 타입을 unordered_map의 key로 사용하려면?
✅ 심화 답변
- 충돌이 많으면 체이닝 길이가 늘어나 성능이 O(n)까지 악화됨
- operator==와 std::hash의 특수화를 제공해야 함
🔷 set, multiset의 차이점은?
🔹 모범 답변
- set: 중복 허용 안 함, 자동 정렬
- multiset: 중복 허용, 동일 키 여러 개 저장 가능
🔸 심화 질문
- multiset은 어떤 상황에서 유리한가요?
- map과 set의 내부 차이는?
✅ 심화 답변
- 동일 값을 중복 저장해야 할 때 유용 (ex: 이벤트 타임스탬프 기록 등)
- map은 key-value 쌍 저장, set은 key 자체가 value
🔷 lower_bound, upper_bound는 어떻게 동작하나요?
🔹 모범 답변
- lower_bound: 이상(≥) 인 첫 위치
- upper_bound: 초과(>) 인 첫 위치
- 둘 다 정렬된 컨테이너에서 이진 탐색 기반으로 동작
🔸 심화 질문
- unordered_map에서 lower_bound가 없는 이유는?
✅ 심화 답변
- unordered 계열은 해시 기반이라 순서 정보가 없기 때문
🔷 iterator의 종류와 차이점은?
🔹 모범 답변
- Input, Output, Forward, Bidirectional, RandomAccess
- vector는 RandomAccess, list는 Bidirectional
🔸 심화 질문
- iterator category가 성능에 미치는 영향은?
✅ 심화 답변
- 반복자 category는 알고리즘 최적화(예: std::advance)에서 최적 루프 선택 기준이 됨
🔷vector와 list의 차이는 무엇인가요?
🔹 모범 답안:
- vector는 동적 배열 기반으로 **임의 접근(O(1))**이 빠르고, 캐시 효율성이 좋습니다.
- list는 이중 연결 리스트로, **삽입/삭제 O(1)**이 가능하지만 임의 접근은 느립니다(O(n)).
- vector는 메모리가 연속적이고, list는 노드가 산개되어 있어 캐시 성능이 떨어질 수 있습니다.
게임 클라이언트에서는 대량의 객체를 순회하며 업데이트하거나 렌더링할 때 vector가 더 적합합니다.
🔸 심화 질문:
- vector에서 중간 삽입이 빈번하다면 어떤 문제가 발생하나요?
- list가 필요 이상으로 느려지는 실제 사례를 설명해보세요.
- C++20의 std::span이 vector와 어떤 차이를 가지나요?
🔷map과 unordered_map의 차이는?
🔹 모범 답안:
- map은 Red-Black Tree 기반으로 항상 정렬된 상태 유지, 삽입/탐색/삭제가 O(log n).
- unordered_map은 Hash Table 기반으로 정렬 X, 평균 탐색/삽입 O(1), 충돌 발생 시 최악 O(n).
- unordered_map은 key가 정렬을 요구하지 않는 경우 빠른 검색 성능을 보입니다.
예를 들어, 게임 내 유저 ID에 빠르게 접근할 땐 unordered_map, 순서가 중요하면 map 사용
🔸 심화 질문:
- hash 충돌이 많이 발생하면 어떤 일이 생기나요? 어떻게 방지하나요?
- unordered_map을 사용할 때 key 타입에 어떤 제약이 있나요?
- map이 operator<를 요구하는 이유는?
🔷vector vs deque
🔹 모범 답안:
- vector는 **push_back()**은 O(1)이지만 **push_front()**는 O(n)
- deque는 앞뒤 모두 O(1) 삽입/삭제 가능
- 내부적으로는 분할된 블록 구조로 되어 있어 메모리 연속성이 떨어짐 → vector보다 캐시 효율이 낮을 수 있음
게임 클라이언트에서 이벤트 로그를 앞뒤로 추가/삭제할 경우 deque가 적합
🔸 심화 질문:
- deque는 어떤 구조로 앞뒤 삽입을 빠르게 지원하나요?
- 메모리 재할당 비용은 deque가 더 낮은가요?
- deque가 사용되는 실제 사례는?
🔷 set과 unordered_set의 차이
🔹 모범 답안:
- set: 정렬된 원소 저장, 중복 불가, 삽입/탐색 O(log n)
- unordered_set: 정렬 없음, 해시 기반, 탐색 평균 O(1), 충돌 시 성능 저하
- set은 operator< 필요, unordered_set은 hash 함수 필요
🔸 심화 질문:
- set을 사용할 때 커스텀 비교자를 지정하려면 어떻게 하나요?
- unordered_set에서 커스텀 객체를 key로 쓰려면 어떤 연산자/함수가 필요한가요?
- 멀티스레드 환경에서 unordered_set을 사용할 때 고려할 점은?
🔷 lower_bound, upper_bound는 어떤 함수인가요?
🔹 모범 답안:
- lower_bound: **이상(≥)**인 첫 번째 요소의 iterator 반환
- upper_bound: **초과(>)**인 첫 번째 요소의 iterator 반환
- set, map, vector + binary_search에 유용하게 사용됨
- 정렬된 컨테이너에서만 동작하며, 이진 탐색 기반으로 동작
🔸 심화 질문:
- lower_bound를 unordered_map에서 쓸 수 없는 이유는?
- map<int, vector> 구조에서 특정 범위 검색 시 lower_bound를 어떻게 활용하나요?
- equal_range()는 어떤 상황에서 유용한가요?
🔷STL 컨테이너 성능을 게임에 어떻게 적용하나요?
🔹 모범 답안:
- vector: 프레임마다 매번 순회하며 업데이트하는 유닛 리스트 등에 사용
- unordered_map: 유저 ID → 정보 매핑, 리소스 테이블 관리
- set: 타겟팅 우선순위, 정렬된 상태 유지가 필요한 경우
- deque: 커맨드 큐, 히스토리 로그
- list: 오브젝트 풀링에서 연결 리스트로 사용되는 경우 (주의: 대부분 vector로 대체 가능)
✅ 정리 요약
- 탐색 속도 필요 → unordered_map
- 정렬된 상태 유지 → map, set
- 빈번한 삽입/삭제 → list (대부분 vector로 커버 가능)
- 연속적인 순차처리 + 캐시 효율 → vector
- 앞뒤 삽입이 필요 → deque
✅ 6. 성능 및 최적화
🔷 inline 함수와 매크로의 차이점은?
🔹 모범 답변
- inline 함수는 타입 안전 + 디버깅 가능 + 함수처럼 동작
- 매크로는 단순 치환, 타입 체크 불가, 디버깅 어려움
🔸 심화 질문
- inline 함수가 항상 인라인되나요?
✅ 심화 답변
- 아닙니다. 컴파일러는 최적화 판단에 따라 인라인 여부를 결정하며, __forceinline은 힌트일 뿐 보장되지 않음
🔷 캐시 미스와 false sharing이란?
🔹 모범 답변
- 캐시 미스: 필요한 데이터가 캐시에 없어 메모리 접근이 발생
- false sharing: 서로 다른 쓰레드가 같은 캐시 라인의 다른 데이터를 쓰며 성능 저하
🔸 심화 질문
- false sharing을 방지하는 방법은?
✅ 심화 답변
- 쓰레드별 데이터 사이에 padding 또는 alignas(64)를 삽입해 캐시 라인 분리
🔷 memory pool은 언제 쓰이고 어떤 장점이 있나요?
🔹 모범 답변
- 자주 생성/삭제되는 객체를 미리 할당해두고 재사용
- new/delete 비용 감소, 메모리 단편화 방지, 캐시 적중률 향상
🔸 심화 질문
- STL 컨테이너에 custom allocator를 적용할 수 있나요?
✅ 심화 답변
- 가능함. std::vector<T, MyAllocator> 형태로 정의해 메모리 풀과 연동 가능
🔷 게임 클라이언트에서 object pooling이 필요한 이유는?
🔹 모범 답변
- 게임에서는 총알, 이펙트, NPC 등 객체 생성/삭제가 빈번
- 오브젝트 풀을 사용하면 생성 비용과 GC 오버헤드를 줄이고 성능을 안정화시킬 수 있음
🔸 심화 질문
- 오브젝트 풀에서 상태 초기화는 누가 책임지나요?
✅ 심화 답변
- 보통 객체가 반환될 때 초기화 함수를 호출하거나, 사용할 때 Reset()을 호출함 → 책임 분리 중요
🔷 move semantics와 rvalue reference는 어떻게 활용하나요?
🔹 모범 답변
- 값의 복사 비용이 큰 객체에서 std::move를 사용해 자원의 소유권 이전
- 임시 객체(rvalue)에 대해 복사 대신 이동을 수행
🔸 심화 질문
- std::move가 정말로 객체를 이동시키나요?
✅ 심화 답변
- 아니요. 단순히 rvalue로 변환하는 캐스팅 함수입니다. 실제 이동은 이동 생성자/대입 연산자가 수행
✅ 7. 멀티스레딩 및 동기화
🔷 C++11의 std::thread는 어떻게 사용하나요?
🔹 모범 답변
std::thread는 C++11에서 도입된 표준 스레드 클래스입니다.
함수, 람다, functor 등을 인자로 받아 스레드를 생성합니다.
🔸 심화 질문
- join()과 detach()의 차이는?
- 스레드 객체를 이동시킬 수 있나요?
✅ 심화 답변
- join()은 해당 스레드가 종료될 때까지 대기, detach()는 백그라운드로 분리
- std::thread는 복사 불가, 이동 가능 (std::move 필요)
🔷 mutex, lock_guard, unique_lock의 차이점은?
🔹 모범 답변
- std::mutex: 수동 lock()/unlock() 필요
- lock_guard: 생성 시 락, 소멸 시 자동 해제 (RAII)
- unique_lock: 조건 변수와 함께 사용 가능하며 더 유연함
🔸 심화 질문
- scoped_lock은 어떤 상황에서 사용되나요?
- try_lock()은 언제 유용한가요?
- unique_lock이 lock_guard보다 무거운 이유는?
- 데드락은 어떤 상황에서 발생하고 이를 어떻게 방지하나요?
✅ 심화 답변
- scoped_lock은 여러 mutex를 한 번에 안전하게 잠글 때 사용 (deadlock 방지)
- try_lock()은 즉시 락을 얻을 수 없을 경우 다른 작업을 수행할 수 있어 유용
- unique_lock은 lock/unlock을 수동으로 제어 가능하므로 내부 상태를 더 많이 저장함
- 순서 없이 여러 락을 획득하거나 락 획득 후 블로킹 호출 시 데드락 발생 → 락 획득 순서 고정, try_lock, timeout 전략 사용
🔷 데드락(deadlock)이 발생하는 상황과 해결 방법은?
🔹 모범 답변
데드락은 두 개 이상의 스레드가 서로가 가진 자원을 기다릴 때 발생합니다.
예: A가 B의 mutex를, B가 A의 mutex를 기다릴 때
해결 방법:
- 락 획득 순서 고정
- std::lock() 사용
- timeout 기반 락 시도 (try_lock_for)
🔸 심화 질문
- 교착 상태가 반드시 발생하는 네 가지 조건은?
✅ 심화 답변
- 상호 배제
- 점유와 대기
- 비선점
- 순환 대기
→ 이 조건 중 하나라도 제거하면 데드락 방지 가능
🔷 condition_variable은 언제 사용하나요?
🔹 모범 답변
- 쓰레드 간 이벤트 기반 통신이 필요할 때 사용
- 생산자-소비자 패턴 구현에 필수
- wait()은 unique_lock과 함께 사용되며, 조건이 만족될 때까지 대기
🔸 심화 질문
- spurious wakeup이란 무엇인가요?
✅ 심화 답변
- 조건이 false인데도 wait()가 예기치 않게 깨는 현상
- 이를 방지하기 위해 wait(lock, condition) 형식으로 사용해야 함
🔷 멀티스레딩 환경에서 게임 데이터를 안전하게 다루려면?
🔹 모범 답변
- 쓰레드 간 공유 데이터 접근 시 반드시 **동기화 도구(mutex, atomic 등)**를 사용해야 함
- lock-free 구조가 가능하다면 atomic이나 ring buffer 기반 구조 활용 권장
🔸 심화 질문
- lock-free queue는 어떻게 작동하나요?
✅ 심화 답변
- CAS(compare-and-swap) 기반으로 경쟁 없이 데이터 삽입/삭제
- 다중 생산자/소비자에서는 메모리 순서와 캐시 일관성에 주의 필요
✅ 8. 게임 클라이언트 특화 C++ 질문
🔷 게임 루프는 어떻게 구성되며 어떤 방식으로 최적화하나요?
🔹 모범 답변
기본 루프는 다음과 같은 흐름으로 구성됩니다:
while (running) {
ProcessInput();
Update(deltaTime);
Render();
}
- 고정 timestep과 가변 timestep을 분리해 처리 (physics vs logic)
- 최적화 포인트: deltaTime 관리, 렌더링 최적화, 이벤트 배치
🔸 심화 질문
- Fixed timestep vs variable timestep의 차이는?
✅ 심화 답변
- fixed는 안정적인 물리 시뮬레이션, variable은 입력 반응성 증가
- 둘을 병행하려면 accumulator 패턴 활용
🔷 상태 머신(FSM)은 게임에서 어떻게 구현하나요?
🔹 모범 답변
FSM은 상태(state)와 상태 간 전이(transition)를 코드로 명시하여,
플레이어/몬스터 AI, UI 흐름 등에 사용됩니다.
cpp
복사편집
enum class State { Idle, Attack, Die };
🔸 심화 질문
- FSM이 복잡해질 때 어떻게 확장하나요?
✅ 심화 답변
- 상태 클래스 추상화(State Pattern), 또는 Behavior Tree 등 고차원 설계로 확장
🔷 메시지 큐 시스템을 C++로 어떻게 설계하나요?
🔹 모범 답변
- 메시지 구조체(Message { Type, Payload })를 정의
- Queue에 삽입하고, 시스템 간 독립적으로 처리
- 이벤트 디커플링, 비동기 처리에 유리
🔸 심화 질문
- 메시지 큐가 병목이 될 수 있는 상황은?
✅ 심화 답변
- lock 기반 큐 사용 시 동기화 병목 발생 → lock-free 구조나 double buffering으로 개선
🔷 컴포넌트 기반 구조(Entity-Component System)는 어떻게 구현하나요?
🔹 모범 답변
- Entity: ID만 갖는 단순 객체
- Component: 데이터만 보유
- System: 로직만 수행
- 데이터 중심 구조로 캐시 효율과 병렬성 확보
🔸 심화 질문
- OOP보다 ECS가 성능 면에서 유리한 이유는?
✅ 심화 답변
- SoA(Structure of Arrays) 방식으로 구성 → 대량의 객체에 대해 연산 시 캐시 적중률 극대화
- 데이터 중심 접근 덕분에 SIMD, 병렬화 용이
🔷 실시간 네트워크 패킷을 안전하게 처리하려면?
🔹 모범 답변
- 수신 버퍼 → 패킷 파싱 → 메시지 큐에 삽입
- 버퍼 오버플로, 비동기 경합, 패킷 순서 문제를 모두 고려해야 함
🔸 심화 질문
- TCP/UDP 각각 어떤 데이터에 적합한가요?
✅ 심화 답변
- TCP: 채팅, 인벤토리 등 신뢰성 요구 데이터
- UDP: 위치 동기화, 조작 명령 등 빠른 전송이 우선인 경우
✅ 9. 기타 주제
🔷 volatile 키워드의 의미는?
🔹 모범 답변
- 변수 값이 외부 요인(하드웨어 등)에 의해 변경될 수 있으므로 최적화 제외 요청
- 멀티스레드 메모리 동기화 보장 수단은 아님
🔸 심화 질문
- volatile이 멀티스레드에서 동기화 보장 안 되는 이유?
✅ 심화 답변
- CPU 캐시, compiler reorder 등의 문제로 메모리 장벽(memory barrier) 없이 일관성 보장 안 됨
- 대신 std::atomic 사용 필요
🔷 undefined behavior의 예시와 방지 방법은?
🔹 모범 답변
- ex) 초기화되지 않은 변수 사용, 배열 범위 초과 접근, double delete 등
- 컴파일러 최적화 결과 예측 불가 → 프로그램이 정상 작동하지 않음
🔸 심화 질문
- UB가 발생해도 잘 실행되는 것처럼 보이는 이유는?
✅ 심화 답변
- 컴파일러가 최적화를 공격적으로 수행하며, UB 상황에서는 어떤 동작도 보장되지 않음
🔷 함수 포인터와 람다 함수의 차이는?
🔹 모범 답변
- 함수 포인터: 단순 주소 참조
- 람다: 상태 캡처 가능, operator() 오버로드
- 람다는 std::function과 함께 사용 가능
🔸 심화 질문
- 캡처된 변수는 언제까지 유지되나요?
✅ 심화 답변
- 람다 객체 수명이 끝날 때까지 유지됨 → reference 캡처 주의 필요
🔷 RTTI(Runtime Type Identification)는 어떻게 작동하나요?
🔹 모범 답변
- typeid, dynamic_cast 등을 통해 런타임에 실제 타입 확인
- RTTI를 위해 클래스에 가상 함수가 있어야 함
🔸 심화 질문
- RTTI가 비활성화된 빌드에서 dynamic_cast는 어떻게 동작하나요?
✅ 심화 답변
- 실패하거나 프로그램이 크래시 날 수 있음 → RTTI 사용 여부 컴파일 옵션 확인 필요
🔷 커스텀 할당자(Custom Allocator)는 언제 사용하나요?
🔹 모범 답변
- 메모리 풀, 오브젝트 풀 등을 구현하여 할당 정책 최적화
- STL 컨테이너에 삽입해 할당 전략 통제 가능
🔸 심화 질문
- STL 컨테이너에서 커스텀 할당자 삽입 방식은?
✅ 심화 답변
- std::vector<T, MyAllocator> 형태로 정의
- allocator는 allocate, deallocate, construct 등 메서드 오버라이드 필요
🔷 함수 템플릿과 클래스 템플릿의 차이점은?
🔹 모범 답안:
- 함수 템플릿은 함수에 들어오는 매개변수 타입에 따라 자동 추론 가능
- 클래스 템플릿은 객체 생성 시 명시적으로 타입을 지정해야 함
🔸 심화 질문:
- 템플릿 인자가 많아질 경우 디버깅이 어려워지는 이유는?
- 함수 템플릿 오버로딩과 특수화는 어떻게 다르며 언제 사용하나요?
- 클래스 템플릿에서 static 멤버는 어떻게 관리되나요?
✅ 심화 질문 답변:
- 템플릿은 컴파일 타임에 인스턴스화되므로 에러 메시지가 복잡하게 출력됨
- 특수화는 특정 타입에 대한 구현, 오버로딩은 함수 시그니처에 따른 일반적인 분기 처리
- 클래스 템플릿의 static 멤버는 타입 인자마다 독립적으로 생성됨
🔷mutex, lock_guard, unique_lock의 차이는?
🔹 모범 답안:
- std::mutex: 가장 기본적인 락 객체, lock()과 unlock() 필요
- lock_guard: 생성 시 lock, 소멸 시 unlock (RAII 패턴)
- unique_lock: lock_guard보다 유연하며, 지연 락/재귀/조건변수 연동 가능
🔸 심화 질문:
- unique_lock이 lock_guard보다 무거운 이유는?
- std::scoped_lock은 무엇이고 어떤 상황에서 쓰나요?
- 데드락은 어떤 상황에서 발생하고 이를 어떻게 방지하나요?
✅ 심화 질문 답변:
- unique_lock은 lock/unlock을 수동으로 제어 가능하므로 내부 상태를 더 많이 저장함
- scoped_lock은 여러 개의 mutex를 동시에 락할 때 데드락 방지용으로 사용됨
- 순서 없이 여러 락을 획득하거나 락 획득 후 블로킹 호출 시 데드락 발생 → 락 획득 순서 고정, try_lock, timeout 전략 사용
🔷Rule of Three, Five, Zero란?
🔹 모범 답안:
- Rule of Three: 소멸자, 복사 생성자, 복사 대입 연산자 중 하나를 정의하면 나머지도 정의해야 함
- Rule of Five: C++11 이후, 이동 생성자와 이동 대입 연산자까지 포함
- Rule of Zero: RAII와 스마트 포인터 등을 활용해 자원 관리를 사용자 정의하지 않도록 설계
🔸 심화 질문:
- 이동 생성자와 복사 생성자가 모두 있을 때 호출 우선순위는?
- Rule of Five를 지키지 않으면 어떤 문제가 생기나요?
- 사용자 정의 소멸자만 정의했을 때 어떤 일이 발생할 수 있나요?
✅ 심화 질문 답변:
- rvalue가 전달되면 이동 생성자가, lvalue가 전달되면 복사 생성자가 호출됨
- 자원 중복 해제, double delete, move 이후 dangling pointer 등의 문제가 생김
- 사용자 정의 소멸자만 정의하고 복사 생성자를 정의하지 않으면 컴파일러가 기본 복사 생성자를 만들지 않아 객체 복사가 막힘
🔷싱글턴 패턴은 어떻게 구현하나요?
🔹 모범 답안:
- 전역에서 하나의 인스턴스만 존재하도록 보장하는 패턴
- 게임에서는 리소스 관리자, 설정 관리자, 상태 관리자 등에 사용
🔸 심화 질문:
- 싱글턴의 단점과 테스트 측면의 문제는?
- 멀티스레딩 환경에서 싱글턴을 안전하게 만들려면?
- 싱글턴 패턴은 반드시 필요한가요?
✅ 심화 질문 답변:
- 전역 의존성 증가, 테스트 어려움, 수명 관리 어려움 등
- C++11의 정적 지역 변수 초기화는 스레드 안전 보장됨
- 대부분의 경우 의존성 주입(DI) 구조나 서비스 로케이터 패턴이 더 나은 선택
✅ 구조체 패딩 관련 면접 질문
🔷 구조체 패딩이란 무엇인가요?
🔹 모범 답변
구조체 패딩은 CPU의 메모리 정렬 규칙에 따라, 구조체 멤버 사이 또는 끝에 빈 공간이 삽입되는 현상입니다.
- 이유: 대부분의 CPU는 특정 데이터 타입을 **자연스러운 정렬(boundary)**에 맞춰 접근할 때 더 빠릅니다.
- 정렬 단위를 맞추기 위해 컴파일러가 자동으로 패딩 바이트를 삽입합니다.
🔸 심화 질문
- 구조체 패딩이 성능에 어떤 영향을 주나요?
- #pragma pack은 어떤 역할을 하나요?
- false sharing과 패딩은 어떤 관계가 있나요?
- C++20 [[no_unique_address]]와 구조체 크기 최적화는 어떤 관계가 있나요?
✅ 심화 질문에 대한 답변
- 구조체 패딩이 성능에 어떤 영향을 주나요?
- 패딩으로 인한 메모리 낭비가 발생하고, 캐시 효율도 떨어질 수 있습니다.
- 멤버 재배치를 통해 padding을 줄이면 데이터 캐시 적중률이 향상되어 성능이 좋아질 수 있습니다.
- 예: 수천 개의 객체를 순회할 때 불필요한 패딩으로 인해 L1/L2 캐시에 덜 들어가게 됨
- #pragma pack은 어떤 역할을 하나요?
- 구조체의 **정렬 단위(boundary)**를 지정하여 패딩을 줄이거나 제거합니다.
- 다만 성능 저하 및 CPU misaligned access 문제가 발생할 수 있으므로 신중히 사용해야 합니다.
- false sharing과 패딩은 어떤 관계가 있나요?
- false sharing은 서로 다른 쓰레드가 동일한 캐시 라인을 공유하며 불필요한 캐시 동기화 비용이 발생하는 현상입니다.
- 이 문제를 막기 위해 의도적으로 padding을 삽입하여 캐시 라인 경계를 맞추는 경우가 많습니다.
- C++20 [[no_unique_address]]와 구조체 크기 최적화는 어떤 관계가 있나요?
- [[no_unique_address]]는 빈 멤버 객체를 구조체 내에서 패딩 없이 중첩시킬 수 있게 해줍니다.
- 이를 활용하면 구조체 크기를 줄이고 불필요한 메모리 낭비를 줄일 수 있습니다.
🔚 요약 정리
- 구조체 패딩은 메모리 정렬 최적화를 위한 필수 과정
- 멤버 재배치를 통해 줄일 수 있고, #pragma pack으로 강제 제어 가능
- 게임 클라이언트에서 대량의 구조체 배열을 다룰 경우, 캐시 효율과 메모리 최적화를 위해 패딩 고려는 매우 중요
✅ 객체지향 프로그래밍(OOP)에 대한 일반 질문
🔷 객체지향 프로그래밍이란 무엇인가요?
🔹 모범 답변
객체지향 프로그래밍(OOP, Object-Oriented Programming)은 현실 세계의 사물(Object)을 추상화하여 소프트웨어로 표현하는 프로그래밍 패러다임입니다.
OOP는 다음의 4대 특성을 중심으로 구성됩니다:
- 캡슐화(Encapsulation): 내부 구현은 숨기고 인터페이스만 외부에 제공
- 상속(Inheritance): 기존 클래스를 기반으로 기능을 확장
- 다형성(Polymorphism): 동일한 인터페이스로 다양한 구현 호출 가능
- 추상화(Abstraction): 중요한 개념만 표현하고 세부 구현은 감춤
🔸 심화 질문
- 캡슐화와 추상화의 차이는 무엇인가요?
- OOP의 단점이나 한계는 무엇이 있나요?
- 게임 개발에서 OOP 대신 ECS(Entity Component System)를 사용하는 이유는?
✅ 심화 답변
- 캡슐화와 추상화의 차이는?
- 캡슐화는 객체 내부 구현을 감추고 외부에 일관된 인터페이스만 제공하는 행위 (데이터 보호 목적)
- 추상화는 복잡한 시스템에서 핵심 개념만 선택적으로 표현하여 불필요한 세부사항은 감추는 것 (개념 단순화 목적)
요약:
추상화는 무엇을 보여줄 것인가,
캡슐화는 어떻게 숨길 것인가에 집중합니다.
- OOP의 단점이나 한계는?
- 성능 오버헤드: 가상 함수 호출, 메모리 분산 배치로 인해 캐시 비효율 발생
- 복잡성 증가: 깊은 상속 구조는 디버깅과 유지보수를 어렵게 함
- 데이터 중심 처리 비효율: 게임 클라이언트에서는 update() 등 동일 연산 반복이 많아 구조 분리 불리
- 게임 개발에서 ECS를 사용하는 이유는?
- OOP는 객체 간 강한 결합과 가상 함수 호출 비용이 존재
- ECS는 데이터와 로직을 분리하여 **데이터 중심 구조(SoA, Structure of Arrays)**를 만들고, CPU 캐시 효율을 극대화
- Update() 호출 비용 감소, SIMD 최적화 가능 등 성능상 이점
✅ 추가적으로 나올 수 있는 응용형 OOP 질문
- 추상화와 인터페이스의 차이는?
- C++에서 다형성을 사용하는 경우와 피하는 경우는?
- 상속보다는 조합(composition)이 더 나은 경우는 언제인가요?
- OOP에서 SOLID 원칙은 무엇인가요?