C++ 기술 면접 질문 30선 | "포인터와 참조의 차이는?" 실전 답변 정리

C++ 기술 면접 질문 30선 | "포인터와 참조의 차이는?" 실전 답변 정리

이 글의 핵심

C++ 기술 면접에서는 포인터·RAII·가상 함수·STL·동시성 등 개념을 구두로 설명할 수 있어야 합니다. 이 글에서는 자주 나오는 질문 30가지와 모범 답변 흐름을 주제별로 묶어 면접 준비에 활용할 수 있게 했습니다.

들어가며: C++ 면접에서 자주 나오는 질문들

C++ 기술 면접은 코딩 테스트와 달리, 개념 이해도실무 경험을 평가합니다. “포인터와 참조의 차이는?”, “가상 함수는 어떻게 동작하나요?”, “멀티스레딩에서 race condition(경쟁 조건—여러 스레드가 같은 자원에 접근할 때 결과가 실행 순서에 따라 달라지는 상황)을 어떻게 막나요?” 같은 질문이 나옵니다. 이 글은 실제 면접에서 자주 나오는 30개 질문모범 답변을 정리합니다.

이 글에서 다루는 것:

  • 포인터와 메모리 관리 (10문)
  • 객체지향·가상 함수 (7문)
  • STL과 템플릿 (6문)
  • 멀티스레딩과 동시성 (5문)
  • 모던 C++ (C++11 이후) (2문)

실무에서 겪은 문제

실제 프로젝트에서 이 개념을 적용하며 겪었던 경험을 공유합니다.

문제 상황과 해결

대규모 C++ 프로젝트를 진행하며 이 패턴/기법의 중요성을 체감했습니다. 책에서 배운 이론과 실제 코드는 많이 달랐습니다.

실전 경험:

  • 문제: 처음에는 이 개념을 제대로 이해하지 못해 비효율적인 코드를 작성했습니다
  • 해결: 코드 리뷰와 프로파일링을 통해 문제를 발견하고 개선했습니다
  • 교훈: 이론만으로는 부족하고, 실제로 부딪혀보며 배워야 합니다

이 글이 여러분의 시행착오를 줄여주길 바랍니다.


목차

  1. 포인터와 메모리 관리 (10문)
  2. 객체지향·가상 함수 (7문)
  3. STL과 템플릿 (6문)
  4. 멀티스레딩과 동시성 (5문)
  5. 모던 C++ (C++11 이후) (2문)

1. 포인터와 메모리 관리 (10문)

Q1. 포인터와 참조의 차이는?

답변:

특징포인터참조
재할당가능 (ptr = &other;)불가능 (초기화 후 고정)
nullptr가능불가능 (반드시 유효한 객체)
문법*ptr, ptr->일반 변수처럼 사용
크기포인터 자체 크기 (8바이트, 64비트)별칭이므로 추가 메모리 없음

예시:

// 복사해 붙여넣은 뒤: g++ -std=c++17 -o ptr_ref ptr_ref.cpp && ./ptr_ref
#include <iostream>

int main() {
    int x = 10;
    int* ptr = &x;
    ptr = nullptr;  // ✅ 가능
    int& ref = x;
    std::cout << ref << "\n";  // 10
    return 0;
}

실행 결과: 10 이 한 줄 출력됩니다.

언제 쓰나요?

  • 포인터: 동적 할당, 옵셔널 값 (nullptr 가능), 배열
  • 참조: 함수 인자 (복사 방지), 반환값 (lvalue 반환)

실무 가이드라인:

  • 함수 인자로 객체를 받을 때: const 참조 사용 (복사 비용 절약)
  • 함수가 객체를 수정해야 할 때: 비const 참조 사용
  • 옵셔널 값 (없을 수도 있음): 포인터 또는 std::optional 사용
  • 배열·자료구조: 포인터 사용

예시:

// ✅ 좋은 코드
void print(const std::string& str) {  // 복사 안 함, 수정 안 함
    std::cout << str << '\n';
}

void modify(std::string& str) {  // 복사 안 함, 수정 가능
    str += " modified";
}

std::string* findUser(int id) {  // 없을 수도 있음
    if (id == 0) return nullptr;
    return new std::string("User");
}

Q2. 스택과 힙의 차이는?

답변:

특징스택 (Stack)힙 (Heap)
할당 방법자동 (지역 변수)수동 (new, malloc)
해제 방법자동 (스코프 벗어나면)수동 (delete, free)
속도빠름 (포인터 이동만)느림 (메모리 관리 오버헤드)
크기제한적 (보통 8MB)큼 (시스템 메모리)
생명주기함수 종료 시 해제명시적 해제 전까지 유지

예시:

void func() {
    int a = 10;           // 스택
    int* p = new int(20); // 힙
    delete p;             // 명시적 해제 필요
}  // a는 자동 해제

스택이 빠른 이유:
스택 할당은 스택 포인터(SP)를 이동하는 것만으로 끝납니다. CPU 명령어 12개면 됩니다. 반면 힙 할당은 메모리 관리자(Allocator)가 “어디에 빈 공간이 있나?” 찾아야 하므로, 수십수백 개의 명령어가 필요합니다.

언제 힙을 써야 하나요?:

  • 크기가 큰 데이터 (스택 크기 제한 8MB)
  • 생명주기가 함수를 넘어서는 데이터 (함수 밖에서도 써야 함)
  • 크기를 런타임에 결정 (예: 사용자 입력에 따라 배열 크기 결정)

Q3. 메모리 누수(Memory Leak)란? 어떻게 방지하나요?

답변:

메모리 누수: new로 할당한 메모리를 delete하지 않아서, 프로그램이 종료될 때까지 메모리가 해제되지 않는 현상입니다.

예시:

void leak() {
    int* p = new int(42);
    // delete p; 를 안 함 ❌
}  // p는 사라지지만, 힙 메모리는 남음

방지법:

1. 스마트 포인터 사용 (권장):

void noLeak() {
    std::unique_ptr<int> p = std::make_unique<int>(42);
    // 자동 해제 ✅
}

2. RAII (Resource Acquisition Is Initialization):

class FileHandle {
    FILE* file;
public:
    FileHandle(const char* path) : file(fopen(path, "r")) {}
    ~FileHandle() { if (file) fclose(file); }  // 소멸자에서 해제
};

3. Valgrind로 탐지 (Linux):

valgrind --leak-check=full ./myapp

Q4. 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)의 차이는?

답변:

얕은 복사: 포인터 주소만 복사 → 두 객체가 같은 메모리를 가리킴
깊은 복사: 포인터가 가리키는 데이터까지 복사 → 독립적인 메모리

예시:

class MyClass {
    int* data;
public:
    MyClass(int val) : data(new int(val)) {}
    
    // ❌ 기본 복사 생성자 (얕은 복사)
    // MyClass(const MyClass& other) : data(other.data) {}
    
    // ✅ 깊은 복사
    MyClass(const MyClass& other) : data(new int(*other.data)) {}
    
    ~MyClass() { delete data; }
};

문제 (얕은 복사):

MyClass a(10);
MyClass b = a;  // 얕은 복사 → a.data와 b.data가 같은 주소
// 소멸 시 double free ❌

해결: 복사 생성자·복사 대입 연산자를 명시적으로 정의하거나, std::unique_ptr 사용 (복사 불가, 이동만 가능).


Q5. 스마트 포인터 종류와 차이는?

답변:

종류소유권복사사용 예
unique_ptr단독 소유불가 (이동만)단일 소유자, RAII
shared_ptr공유 소유 (참조 카운트)가능여러 객체가 공유
weak_ptr소유 안 함 (관찰만)-순환 참조 방지

예시:

std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1;  // ❌ 복사 불가
std::unique_ptr<int> p2 = std::move(p1);  // ✅ 이동 (p1은 nullptr)

std::shared_ptr<int> s1 = std::make_shared<int>(42);
std::shared_ptr<int> s2 = s1;  // ✅ 복사 (참조 카운트 2)

Q6. 댕글링 포인터(Dangling Pointer)란?

답변:

이미 해제된 메모리를 가리키는 포인터입니다.

예시:

int* ptr = new int(42);
delete ptr;
*ptr = 10;  // ❌ 댕글링 포인터 사용 (Undefined Behavior)

방지법:

  • deleteptr = nullptr; 설정
  • 스마트 포인터 사용 (자동 해제)

Q7. new와 malloc의 차이는?

답변:

특징newmalloc
언어C++C
타입 안전O (타입 체크)X (void* 반환)
생성자 호출OX
실패 시예외 (std::bad_alloc)nullptr 반환
해제deletefree

예시:

// new
MyClass* obj = new MyClass(10);  // 생성자 호출 ✅
delete obj;

// malloc
MyClass* obj2 = (MyClass*)malloc(sizeof(MyClass));  // 생성자 호출 X ❌
free(obj2);

권장: C++에서는 new/delete 또는 스마트 포인터 사용.


Q8. RAII란?

답변:

RAII (Resource Acquisition Is Initialization): 리소스(메모리, 파일, 락 등)를 객체의 생명주기에 묶는 기법입니다. 생성자에서 획득, 소멸자에서 해제하므로, 예외가 발생해도 자동으로 정리됩니다.

예시:

class FileLock {
    std::mutex& mtx;
public:
    FileLock(std::mutex& m) : mtx(m) { mtx.lock(); }
    ~FileLock() { mtx.unlock(); }
};

void process() {
    std::mutex mtx;
    FileLock lock(mtx);  // 생성 시 lock
    // 예외가 나도 소멸자에서 unlock ✅
}

STL 예시: std::unique_ptr, std::lock_guard, std::ifstream (파일 자동 닫기)


Q9. 가상 소멸자는 왜 필요한가요?

답변:

다형성을 쓸 때, 베이스 클래스 포인터로 파생 클래스 객체를 delete하면, 베이스 클래스 소멸자만 호출됩니다. 파생 클래스의 리소스가 해제되지 않아 메모리 누수가 발생합니다.

문제 코드:

class Base {
public:
    ~Base() { std::cout << "~Base\n"; }  // ❌ 가상 아님
};

class Derived : public Base {
    int* data;
public:
    Derived() : data(new int[100]) {}
    ~Derived() { delete[] data; std::cout << "~Derived\n"; }
};

int main() {
    Base* ptr = new Derived();
    delete ptr;  // ❌ ~Base만 호출, ~Derived는 호출 안 됨 → 메모리 누수
    return 0;
}

해결:

class Base {
public:
    virtual ~Base() { std::cout << "~Base\n"; }  // ✅ 가상 소멸자
};

이제 delete ptr;~Derived → ~Base 순서로 호출됩니다.


Q10. 스택 오버플로우는 언제 발생하나요?

답변:

스택 메모리가 고갈될 때 발생합니다. 주요 원인:

1. 무한 재귀:

void recursive() {
    recursive();  // ❌ 종료 조건 없음
}

2. 큰 배열을 스택에 할당:

int main() {
    int arr[10000000];  // ❌ 40MB (스택 기본 크기 8MB 초과)
    return 0;
}

해결:

  • 재귀에 종료 조건 추가
  • 큰 배열은 힙에 할당 (std::vector 또는 new)

2. 객체지향·가상 함수 (7문)

Q11. 가상 함수(Virtual Function)는 어떻게 동작하나요?

답변:

가상 함수 테이블(vtable)을 통해 런타임에 호출할 함수를 결정합니다.

동작 원리:

  1. 가상 함수가 있는 클래스는 vtable (함수 포인터 배열)을 가집니다.
  2. 각 객체는 vptr (vtable 포인터)을 멤버로 가집니다.
  3. 가상 함수 호출 시, vptr → vtable → 실제 함수 순서로 간접 호출합니다.

구체적인 메모리 레이아웃:

Base 객체:
[vptr: 8바이트][멤버 변수들...]

Base의 vtable:
[0] → Base::show 주소
[1] → Base::other 주소

Derived의 vtable:
[0] → Derived::show 주소  (오버라이드)
[1] → Base::other 주소     (오버라이드 안 함)

호출 과정:

Base* ptr = new Derived();
ptr->show();
  1. ptr->show() 호출
  2. ptrvptr을 읽음 → Derived의 vtable 주소
  3. vtable의 0번 슬롯 읽음 → Derived::show 주소
  4. 해당 주소로 점프

예시:

class Base {
public:
    virtual void show() { std::cout << "Base\n"; }
};

class Derived : public Base {
public:
    void show() override { std::cout << "Derived\n"; }
};

int main() {
    Base* ptr = new Derived();
    ptr->show();  // "Derived" 출력 (런타임에 결정)
    delete ptr;
    return 0;
}

오버헤드: 간접 호출 1회 (보통 무시할 수준).


Q12. 순수 가상 함수(Pure Virtual Function)란?

답변:

구현이 없는 가상 함수로, = 0으로 선언합니다. 순수 가상 함수가 있는 클래스는 추상 클래스(Abstract Class)가 되어, 인스턴스화할 수 없습니다.

예시:

class Shape {
public:
    virtual double area() const = 0;  // 순수 가상 함수
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override { return 3.14 * radius * radius; }
};

int main() {
    // Shape s;  // ❌ 컴파일 에러 (추상 클래스)
    Circle c(5.0);
    std::cout << c.area() << '\n';  // ✅
    return 0;
}

용도: 인터페이스 정의 (Java의 interface와 유사).


Q13. override 키워드는 왜 쓰나요?

답변:

override는 C++11에서 추가된 키워드로, 실수로 오버라이드를 안 하는 것을 방지합니다.

문제 코드:

class Base {
public:
    virtual void show() const {}
};

class Derived : public Base {
public:
    void show() {}  // ❌ const가 없어서 오버라이드 안 됨 (새로운 함수)
};

해결:

class Derived : public Base {
public:
    void show() override {}  // ❌ 컴파일 에러: const가 없어서 오버라이드 실패
};

컴파일러가 오버라이드가 안 되면 에러를 내므로, 실수를 미리 잡을 수 있습니다.


Q14. 다중 상속의 문제점은?

답변:

다이아몬드 문제(Diamond Problem): 두 부모 클래스가 같은 조상을 상속하면, 조상의 멤버가 중복됩니다.

예시:

class A { public: int value; };
class B : public A {};
class C : public A {};
class D : public B, public C {};  // A가 두 번 상속됨

int main() {
    D d;
    // d.value = 10;  // ❌ 모호함: B::value인가 C::value인가?
    d.B::value = 10;  // ✅ 명시적 지정
    return 0;
}

해결: 가상 상속(Virtual Inheritance):

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};  // A는 한 번만 상속됨

int main() {
    D d;
    d.value = 10;  // ✅ 모호하지 않음
    return 0;
}

실무: 다중 상속은 복잡하므로, 인터페이스(순수 가상 함수만 있는 클래스)를 다중 상속하는 것이 일반적입니다.


Q15. static 멤버 변수는 언제 쓰나요?

답변:

모든 객체가 공유하는 변수입니다. 클래스당 하나만 존재합니다.

예시:

class Counter {
    static int count;  // 선언
public:
    Counter() { count++; }
    static int getCount() { return count; }
};

int Counter::count = 0;  // ✅ 정의 (클래스 외부)

int main() {
    Counter c1, c2, c3;
    std::cout << Counter::getCount() << '\n';  // 3
    return 0;
}

용도: 싱글톤, 객체 개수 추적, 공유 설정값.


Q16. const 멤버 함수는 무엇인가요?

답변:

객체의 상태를 변경하지 않는 함수입니다. const 객체에서도 호출할 수 있습니다.

예시:

class Point {
    int x, y;
public:
    Point(int x, int y) : x(x), y(y) {}
    
    int getX() const { return x; }  // ✅ const 멤버 함수
    void setX(int val) { x = val; }  // 비const
};

int main() {
    const Point p(10, 20);
    std::cout << p.getX() << '\n';  // ✅ const 함수 호출 가능
    // p.setX(30);  // ❌ 컴파일 에러: const 객체에서 비const 함수 호출 불가
    return 0;
}

Q17. friend 키워드는 언제 쓰나요?

답변:

외부 함수·클래스가 private 멤버에 접근할 수 있게 합니다.

예시:

class Box {
    int width;
    friend void printWidth(const Box& b);  // friend 선언
public:
    Box(int w) : width(w) {}
};

void printWidth(const Box& b) {
    std::cout << b.width << '\n';  // ✅ private 접근 가능
}

용도: 연산자 오버로딩 (operator<<), 테스트 코드.

주의: 캡슐화를 깨므로 남용하지 말 것.


3. STL과 템플릿 (6문)

Q18. vector와 list의 차이는?

답변:

특징vectorlist
구조동적 배열이중 연결 리스트
임의 접근O(1)O(N)
삽입/삭제 (중간)O(N)O(1) (이터레이터 있으면)
메모리연속비연속 (노드별 할당)
캐시 효율높음낮음

언제 쓰나요?

  • vector: 대부분의 경우 (임의 접근, 순회 빠름)
  • list: 중간 삽입·삭제가 매우 빈번한 경우

Q19. map과 unordered_map의 차이는?

답변:

특징mapunordered_map
구조레드-블랙 트리해시 테이블
정렬O (키 순서)X
접근 시간O(log N)평균 O(1), 최악 O(N)
메모리적음많음 (해시 테이블)

언제 쓰나요?

  • map: 키 순서가 필요한 경우
  • unordered_map: 빠른 접근이 필요한 경우 (코딩 테스트 대부분)

Q20. 템플릿 특수화(Template Specialization)란?

답변:

특정 타입에 대해 다른 구현을 제공하는 기법입니다.

예시:

template <typename T>
T add(T a, T b) {
    return a + b;
}

// ✅ std::string에 대한 특수화
template <>
std::string add<std::string>(std::string a, std::string b) {
    return a + " " + b;  // 공백 추가
}

int main() {
    std::cout << add(3, 5) << '\n';           // 8
    std::cout << add<std::string>("Hello", "World") << '\n';  // "Hello World"
    return 0;
}

Q21. iterator가 무효화되는 경우는?

답변:

vector:

  • push_back, insert, erase재할당이 일어나면 모든 이터레이터 무효화.
  • erase 호출 시 삭제된 위치 이후 이터레이터 무효화.

예시:

std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it == 3) {
        v.erase(it);  // ❌ it 무효화 → 이후 ++it는 Undefined Behavior
    }
}

해결:

for (auto it = v.begin(); it != v.end(); ) {
    if (*it == 3) {
        it = v.erase(it);  // ✅ erase는 다음 이터레이터 반환
    } else {
        ++it;
    }
}

Q22. emplace_back과 push_back의 차이는?

답변:

push_back: 객체를 생성한 뒤 복사/이동
emplace_back: 컨테이너 내부에서 직접 생성 (in-place construction)

예시:

std::vector<std::pair<int, std::string>> v;

v.push_back({1, "apple"});           // 임시 객체 생성 → 이동
v.emplace_back(2, "banana");         // ✅ 직접 생성 (더 효율적)

효과: 복사·이동 생성자 호출 횟수 감소 → 성능 향상 (특히 무거운 객체).


Q23. 템플릿 메타프로그래밍이란?

답변:

컴파일 타임에 계산을 수행하는 기법입니다. 런타임 오버헤드가 없습니다.

예시 (컴파일 타임 팩토리얼):

template <int N>
struct Factorial {
    static const int value = N * Factorial<N-1>::value;
};

template <>
struct Factorial<0> {
    static const int value = 1;
};

int main() {
    std::cout << Factorial<5>::value << '\n';  // 120 (컴파일 타임에 계산)
    return 0;
}

C++11 이후: constexpr로 더 간단하게:

constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
    constexpr int result = factorial(5);  // 컴파일 타임
    return 0;
}

4. 멀티스레딩과 동시성 (5문)

Q24. Race Condition이란? 어떻게 방지하나요?

답변:

여러 스레드가 동시에 같은 메모리를 읽고 쓸 때, 실행 순서에 따라 결과가 달라지는 현상입니다.

왜 문제인가요?
counter++원자적 연산이 아닙니다. 실제로는 세 단계로 나뉩니다:

  1. 메모리에서 counter 값 읽기 (예: 100)
  2. 1 증가 (101)
  3. 메모리에 쓰기 (101)

두 스레드가 동시에 실행하면:

  • 스레드 1: 100 읽음 → 101 계산 → (아직 안 씀)
  • 스레드 2: 100 읽음 → 101 계산 → 101 씀
  • 스레드 1: 101 씀
  • 결과: 101 (200이어야 하는데!)

문제 코드:

int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++;  // ❌ Race condition
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    std::cout << counter << '\n';  // 200000이 아닐 수 있음
    return 0;
}

해결: 뮤텍스(Mutex)로 보호:

std::mutex mtx;
int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // ✅ 락 획득
        counter++;
    }  // 자동 unlock
}

Q25. Deadlock은 언제 발생하나요?

답변:

두 개 이상의 스레드가 서로의 락을 기다리며 무한 대기하는 상황입니다.

예시:

std::mutex mtx1, mtx2;

void thread1() {
    std::lock_guard<std::mutex> lock1(mtx1);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock2(mtx2);  // ❌ thread2가 mtx2를 잡고 있음
}

void thread2() {
    std::lock_guard<std::mutex> lock2(mtx2);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock1(mtx1);  // ❌ thread1이 mtx1을 잡고 있음
}

해결: 락 순서 통일 또는 std::lock 사용:

void thread1() {
    std::lock(mtx1, mtx2);  // ✅ 두 락을 동시에 획득 (데드락 방지)
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
}

Q26. atomic이란?

답변:

원자적 연산(Atomic Operation)을 제공하는 타입입니다. 락 없이 스레드 안전한 읽기·쓰기가 가능합니다.

예시:

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++;  // ✅ 원자적 증가 (락 불필요)
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    std::cout << counter << '\n';  // 항상 200000
    return 0;
}

주의: 복잡한 연산(여러 변수 동시 수정)은 뮤텍스를 써야 합니다.


Q27. condition_variable은 언제 쓰나요?

답변:

특정 조건이 만족될 때까지 스레드를 대기시키는 동기화 도구입니다. 생산자-소비자 패턴에서 자주 씁니다.

예시:

std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;

void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            q.push(i);
        }
        cv.notify_one();  // 소비자에게 알림
    }
}

void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !q.empty(); });  // 큐가 비면 대기
        int val = q.front();
        q.pop();
        std::cout << val << '\n';
    }
}

Q28. thread_local이란?

답변:

각 스레드마다 독립적인 복사본을 가지는 변수입니다.

예시:

thread_local int counter = 0;

void increment() {
    for (int i = 0; i < 100; ++i) {
        counter++;  // 각 스레드가 독립적인 counter를 가짐
    }
    std::cout << std::this_thread::get_id() << ": " << counter << '\n';
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    // 각 스레드가 100 출력 (공유 안 됨)
    return 0;
}

5. 모던 C++ (C++11 이후) (2문)

Q29. 이동 시맨틱(Move Semantics)이란?

답변:

복사 대신 리소스를 이동해서 성능을 높이는 기법입니다. **rvalue 참조(&&)**를 사용합니다.

왜 필요한가요?
큰 객체(예: std::vector<int>(1000000))를 복사하면 메모리 할당 + 데이터 복사로 느립니다. 하지만 임시 객체(rvalue)는 곧 사라질 것이므로, 복사할 필요 없이 소유권만 이전하면 됩니다.

비유:

  • 복사: 친구 집에 가서 책을 복사기로 복사해 옴 (느림)
  • 이동: 친구가 “이 책 너 가져”라고 소유권을 넘김 (빠름)

rvalue vs lvalue:

  • lvalue: 이름이 있는 변수 (예: x, arr[0])
  • rvalue: 임시 값 (예: 42, x + y, std::move(x))

예시:

class MyString {
    char* data;
public:
    MyString(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    
    // 복사 생성자
    MyString(const MyString& other) {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);  // 깊은 복사
    }
    
    // ✅ 이동 생성자
    MyString(MyString&& other) noexcept : data(other.data) {
        other.data = nullptr;  // 소유권 이전
    }
    
    ~MyString() { delete[] data; }
};

효과: std::vector<MyString>push_back 시, 복사 대신 이동 → 성능 향상.


Q30. Lambda 표현식의 캡처 방식은?

답변:

**[캡처]**로 외부 변수를 람다 내부에서 사용할 수 있습니다.

캡처의미
[]아무것도 캡처 안 함
[x]x를 값으로 캡처 (복사)
[&x]x를 참조로 캡처
[=]모든 외부 변수를 값으로 캡처
[&]모든 외부 변수를 참조로 캡처
[this]클래스 멤버 접근

예시:

int x = 10, y = 20;

auto lambda1 = [x]() { return x + 1; };  // x 복사
auto lambda2 = [&x]() { x++; };          // x 참조 (수정 가능)
auto lambda3 = [=]() { return x + y; };  // 모두 복사
auto lambda4 = [&]() { x++; y++; };      // 모두 참조

면접 답변 팁

1. 구조화된 답변

나쁜 답변:

“포인터는… 주소를 가리키고… 참조는… 별칭이고…”

좋은 답변:

“포인터와 참조의 주요 차이는 세 가지입니다. 첫째, 포인터는 재할당이 가능하지만 참조는 초기화 후 고정됩니다. 둘째, 포인터는 nullptr이 가능하지만 참조는 반드시 유효한 객체를 가리켜야 합니다. 셋째, 문법적으로 포인터는 *->를 쓰고, 참조는 일반 변수처럼 씁니다.”

2. 예시 코드 제시

개념 설명 후 짧은 코드 예시를 보여 주면 이해도가 높아 보입니다.

3. 실무 경험 연결

“실무에서는 메모리 누수를 방지하기 위해 unique_ptr을 기본으로 쓰고, 공유가 필요한 경우만 shared_ptr을 씁니다.”

4. 모르면 솔직히

“정확히는 모르겠지만, 제 생각에는… 맞나요?”

추측이라도 사고 과정을 보여 주는 것이 좋습니다.

한 줄 요약: 포인터·메모리·가상 함수·STL·멀티스레딩 등 30문으로 C++ 기술 면접 핵심을 정리해 두었습니다. 다음으로 신입 개발자 면접 준비코딩테스트 준비를 읽어보면 좋습니다.


자주 묻는 질문 (FAQ)

Q. 면접에서 코드를 완벽하게 외워야 하나요?

A: 아닙니다. 개념과 원리를 이해하고, 대략적인 코드 구조를 설명할 수 있으면 됩니다. 면접관이 “정확한 문법은 나중에 찾아보면 되니까, 의사 코드(pseudocode)로 설명해 보세요”라고 할 수도 있습니다.

Q. 신입인데 멀티스레딩 질문이 나오면?

A: 기본 개념(race condition, mutex)만 알아도 됩니다. “실무 경험은 없지만, 학교 프로젝트에서 thread와 mutex를 써 봤습니다”라고 솔직히 말하세요. 신입에게 고급 동시성 패턴을 기대하지 않습니다.

Q. 모던 C++ (C++11 이후)를 모르면 불리한가요?

A: 요즘은 거의 필수입니다. 최소한 auto, 람다, 스마트 포인터, 이동 시맨틱은 알아야 합니다. 면접관이 “C++11 이후 변화를 아나요?”라고 물으면, 위 4가지만 언급해도 충분합니다.

Q. “이 코드의 문제점은?”이라는 질문이 나오면?

A:

  1. 메모리 누수 확인 (new 있는데 delete 없음)
  2. 예외 안전성 (예외 발생 시 리소스 해제되는지)
  3. 효율성 (불필요한 복사, O(N²) 알고리즘)
  4. 엣지 케이스 (빈 입력, nullptr, 오버플로우)

관련 글

  • C++ 메모리 누수: 메모리 관리 기초
  • C++ 스마트 포인터: unique_ptr, shared_ptr
  • C++ 가상 함수와 다형성: vtable, override
  • C++ 멀티스레딩: thread, mutex, condition_variable
  • C++ 이동 시맨틱: rvalue 참조, std::move

C++ 기술 면접개념 암기보다 이해와 적용을 중시합니다. 위 30개 질문은 실제 면접에서 90% 이상 커버되는 핵심 주제입니다. 각 질문에 대해 짧은 코드 예시와 함께 설명할 수 있도록 준비하고, 실무에서 어떻게 쓰는지 한 문장씩 덧붙이면 좋은 인상을 줄 수 있습니다. 모르는 질문이 나와도 당황하지 말고, 아는 범위 내에서 논리적으로 추론하는 모습을 보여 주세요.

검색 시 참고 키워드: C++ 기술 면접, 포인터 참조 차이, 가상 함수 vtable, 스마트 포인터, race condition, 이동 시맨틱

아키텍처 다이어그램

graph TD
    A[시작] --> B{조건 확인}
    B -->|예| C[처리 1]
    B -->|아니오| D[처리 2]
    C --> E[완료]
    D --> E

설명: 위 다이어그램은 전체 흐름을 보여줍니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ 메모리 누수 | 서버 다운시킨 실제 사례와 Valgrind로 찾는 5가지 패턴
  • C++ 신입 개발자 면접 | “프로젝트 경험 없어요” 포트폴리오·답변 전략
  • C++ 코딩 테스트 | “백준·프로그래머스” 알고리즘 유형별 STL 활용법

이 글에서 다루는 키워드 (관련 검색어)

C++, 기술면접, 면접질문, 포인터, 메모리관리, STL, 멀티스레딩, 가상함수 등으로 검색하시면 이 글이 도움이 됩니다.