주요 기능 요약

  • Parallel STL

    • 알고리즘의 병렬 실행 정책
    • 대용량 데이터 처리 성능 향상
    • 순차, 병렬, 벡터화 실행 지원
  • 파일 시스템 라이브러리

    • 플랫폼 독립적인 파일/디렉터리 관리
    • 경로 조작 및 파일 작업
    • 파일 상태 및 속성 검사
  • 새로운 데이터 타입

    • std::optional: 값의 존재 여부 표현
    • std::variant: 타입 안전한 유니온
    • std::any: 타입 지움(type-erased) 값 저장소
  • 코드 간결성 개선

    • 구조화된 바인딩: 다중 값 할당
    • if/switch 초기화 구문
    • 접기 표현식(Fold Expressions)
  • 언어 안전성 향상

    • 인라인 변수: ODR 위반 줄임
    • constexpr if: 컴파일 타임 조건부 코드
    • 클래스 템플릿 인수 추론(CTAD)

Parallel STL (병렬 표준 템플릿 라이브러리)

C++17은 STL 알고리즘의 병렬 실행 버전을 도입했습니다. 약 80개의 STL 알고리즘이 실행 정책을 통해 다양한 방식으로 실행될 수 있게 되었습니다.

  • 순차 실행(std::execution::seq): 기존 방식처럼 순차적으로 실행
  • 병렬 실행(std::execution::par): 여러 스레드에서 병렬로 실행
  • 벡터화 실행(std::execution::par_unseq): SIMD 명령어를 활용한 병렬 실행
#include <algorithm>
#include <execution>
#include <vector>

std::vector<int> v = {/* 많은 데이터 */};

// 순차 정렬
std::sort(std::execution::seq, v.begin(), v.end());

// 병렬 정렬
std::sort(std::execution::par, v.begin(), v.end());

// 벡터화된 병렬 정렬
std::sort(std::execution::par_unseq, v.begin(), v.end());

이 기능은 대용량 데이터 처리 성능을 크게 향상시킬 수 있습니다.

표준화된 파일 시스템(std::filesystem)

C++17은 Boost.Filesystem에서 영감을 받은 파일시스템 라이브러리를 표준에 포함시켰습니다. 이를 통해 다음과 같은 작업이 가능해졌습니다.

  • 파일 및 디렉터리 경로 조작
  • 파일 존재 여부 확인
  • 파일 크기 및 권한 확인
  • 디렉터리 탐색
  • 파일 복사, 이동, 삭제
#include <filesystem>
namespace fs = std::filesystem;

// 디렉터리 순회
for (const auto& entry : fs::directory_iterator("/path/to/dir")) {
    std::cout << entry.path() << '\n';
}

// 파일 존재 확인
if (fs::exists("file.txt")) {
    std::cout << "파일 크기: " << fs::file_size("file.txt") << '\n';
}

// 경로 조작
fs::path p = "/home/user/file.txt";
std::cout << "파일명: " << p.filename() << '\n';
std::cout << "디렉터리: " << p.parent_path() << '\n';

이 기능은 플랫폼 독립적인 파일 조작을 가능하게 합니다.

새로운 데이터 타입

C++17은 Boost에서 영감을 받은, 타입 안정성을 높여주는 세 가지 중요한 데이터 타입을 추가했습니다.

std::optional
값이 존재할 수도, 존재하지 않을 수도 있는 상황을 표현합니다. nullptr 대신 사용할 수 있으며, 반환 값의 부재를 더 명확하게 표현합니다.

#include <optional>
#include <string>

std::optional<std::string> fetchUserName(int id) {
    if (userExists(id)) {
        return userName;
    }
    return std::nullopt; // 값 없음
}

// 사용 예
auto name = fetchUserName(42);
if (name) {
    std::cout << "이름: " << *name << '\n';
} else {
    std::cout << "사용자가 존재하지 않습니다\n";
}

std::variant
다양한 타입 중 하나의 값을 저장할 수 있는 타입-안전 유니온입니다. C스타일 유니온과 달리, 어떤 타입이 저장되어 있는지 추적합니다.

#include <variant>
#include <string>

// 여러 타입 중 하나를 저장
std::variant<int, double, std::string> v = "hello";

// 방문자 패턴으로 값 처리
std::visit([](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>)
        std::cout << "정수: " << arg << '\n';
    else if constexpr (std::is_same_v<T, double>)
        std::cout << "실수: " << arg << '\n';
    else if constexpr (std::is_same_v<T, std::string>)
        std::cout << "문자열: " << arg << '\n';
}, v);

std::any
어떤 타입의 값이든 저장할 수 있는 타입-지움(type-erased) 컨테이너입니다. void*보다 안전한 대안입니다.

#include <any>
#include <string>

std::any a = 42;                       // 정수 저장
a = 3.14;                              // 실수로 변경
a = std::string("Hello, world!");      // 문자열로 변경

// 타입 확인 및 접근
if (a.type() == typeid(std::string)) {
    std::cout << std::any_cast<std::string>(a) << '\n';
}

그 외 C++17의 주요 기능

위에서 언급한 기능 외에도 C++17은 다음과 같은 중요한 기능을 추가했습니다.

구조화된 바인딩(Structured Bindings)

여러 값을 한 번에 바인딩 할 수 있는 기능입니다.

// 맵 요소 풀기
auto [key, value] = *map.begin(); 

// 구조체 멤버 풀기
struct Point { int x, y; };
Point p{1, 2};
auto [x, y] = p;

// 배열 풀기
int arr[3] = {1, 2, 3};
auto [a, b, c] = arr;

if/switch문에서의 초기화 구문

if문이나 switch 문 내에서 변수를 초기화하고 그 범위를 제한할 수 있습니다.

// 변수 범위가 if 블록으로 제한됨
if (auto it = map.find(key); it != map.end()) {
    // it을 사용할 수 있음
} else {
    // 여기서도 it을 사용할 수 있음
}
// 여기서는 it을 사용할 수 없음

// switch 문에서도 동일
switch (auto val = getValue(); val) {
    case 1: /* ... */ break;
    case 2: /* ... */ break;
    default: /* ... */ break;
}

인라인 변수(Inline Variables)

헤더 파일에 정의된 변수도 One Definition Rule(ODR) 위반 없이 사용할 수 있게 합니다.

// header.h
inline int globalVar = 42;   // 여러 번 포함되어도 ODR 위반 없음
inline const double PI = 3.14159265358979;

// 클래스 정적 멤버도 인라인화 가능
class Widget {
    static inline int count = 0;  // 헤더에 정의 가능
public:
    Widget() { ++count; }
    static int getCount() { return count; }
};

접기 표현식(Fold Expressions)

가변 템플릿 인자에 대해 이항 연산자를 적용할 수 있는 간결한 구문을 제공합니다.

template<typename... Args>
auto sum(Args... args) {
    return (... + args);  // 단항 좌측 접기: ((args1 + args2) + args3) + ...
}

template<typename T, typename... Args>
bool allEqual(T first, Args... rest) {
    return (... && (first == rest));  // 모든 인자가 first와 같은지 확인
}

// 사용 예
int total = sum(1, 2, 3, 4, 5);  // 15
bool allSame = allEqual(42, 42, 42);  // true

constexpr if

컴파일 시간에 조건부 코드 실행을 가능하게 합니다.

template <typename T>
void process(T& t) {
    if constexpr (std::is_integral_v<T>) {
        // 정수 타입일 때만 컴파일됨
        std::cout << "정수 처리: " << t << '\n';
    } else if constexpr (std::is_floating_point_v<T>) {
        // 부동소수점 타입일 때만 컴파일됨
        std::cout << "실수 처리: " << t << '\n';
    } else {
        // 다른 타입일 때만 컴파일됨
        std::cout << "기타 타입 처리\n";
    }
}

// 사용 예
int i = 42;
double d = 3.14;
std::string s = "hello";

process(i);  // "정수 처리: 42"
process(d);  // "실수 처리: 3.14"
process(s);  // "기타 타입 처리"

'CS' 카테고리의 다른 글

C++14 표준  (1) 2026.03.04
객체지향 SOLID 원칙 5개  (0) 2026.03.03
C++11 표준  (0) 2026.03.03
C++23: The Next C++ Standard  (0) 2026.03.03
정렬 알고리즘  (0) 2026.03.01

C++14 주요 기능 요약

  • 일반화된 람다 표현식
    • 매개 변수에 auto 사용 가능
    • 다양한 타입의 입력 처리
    • 템플릿처럼 동작하는 재사용 가능한 람다
  • 향상된 constexpr
    • 변수 선언 및 수정 허용
    • 제어문(if, switch, 루프) 지원
    • 컴파일 타임 계산 능력 강화
  • 변수 템플릿
    • 타입에 따라 달라지는 상수 정의
    • 템플릿 매개변수화된 변수 선언
    • 수학적 상수의 타입별 최적화
  • 표준 라이브러리 개선
    • std::make_unique 추가
    • 공유 뮤텍스와 Reader-Writer 락
    • 수학적 상수의 타입별 최적화
  • 코드 가독성 향상
    • 이진수 리터럴(0b, 0B 접두사)
    • 숫자 분리자(') 지원
    • 반환 타입 추론과 decltype(auto)

 

일반화된 람다 표현식(Generalized Lambda Expressions)

C++11에서 도입된 람다가 더 강화되었습니다. 이제 매개변수 타입으로 auto를 사용할 수 있어 제네릭 람다를 작성할 수 있게 되었습니다.

// C++11에서는 명시적 타입이 필요했습니다
auto lambda11 = [](int x, int y) { return x + y; };

// C++14에서는 auto를 사용할 수 있습니다
auto lambda14 = [](auto x, auto y) { return x + y; };

// 다양한 타입에 사용 가능
int sum_int = lambda14(5, 3);            // 정수 연산: 8
double sum_double = lambda14(3.14, 2.71); // 실수 연산: 5.85
std::string s = lambda14(std::string("Hello, "), "world!"); // 문자열 연합: "Hello, world!"

이 기능은 템플릿처럼 동작하는 람다를 만들 수 있게 해주어 코드 재사용성을 높입니다.

 

향상된 constexpr

C++11에서 도입된 constexpr의 제한이 완화되었습니다. 이제 constexpr 함수 내에서 다음을 사용할 수 있습니다.

  • 변수 선언 및 수정
  • if 및 switch 문
  • 모든 종류의 루프(for, while, do-while)
  • 비재귀적 람다 표현식
// C++11에서의 constexpr 함수는 단일 return 문만 가능했습니다
constexpr int factorial_cpp11(int n) {
    return n <= 1 ? 1 : (n * factorial_cpp11(n - 1));
}

// C++14에서는 더 복잡한 함수도 가능합니다
constexpr int factorial_cpp14(int n) {
    int result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

// 컴파일 시간에 계산됩니다
constexpr int fact5 = factorial_cpp14(5); // 120

이 개선으로 컴파일 시간 계산이 훨씬 더 강력해졌습니다.

 

변수 템플릿(Variable Template)

C++14에서는 변수도 템플릿화할 수 있게 되었습니다.

template<typename T>
constexpr T pi = T(3.1415926535897932385);

// 다양한 타입으로 사용 가능
float pi_float = pi<float>;       // 3.14159f
double pi_double = pi<double>;    // 3.14159265358979
long double pi_long = pi<long double>; // 더 높은 정밀도로 근사

이 기능은 수학적 상수나 변환 계수와 같은 타입에 의존적인 값을 정의할 때 유용합니다.

 

표준 라이브러리 개선

공유 뮤텍스와 Reader-Writer 락
여러 읽기 작업과 단일 쓰기 작업을 동시에 수행할 수 있는 새로운 동기화 프리미티브가 추가되었습니다.

 

#include <shared_mutex>
#include <thread>

std::shared_mutex mtx;
int shared_data = 0;

// 읽기 작업(여러 스레드가 동시에 수행 가능)
void reader() {
    std::shared_lock<std::shared_mutex> lock(mtx); // 공유 락 획득
    // shared_data 읽기
}

// 쓰기 작업(한 번에 하나의 스레드만 수행 가능)
void writer() {
    std::unique_lock<std::shared_mutex> lock(mtx); // 독점 락 획득
    // shared_data 수정
}

 

이 기능은 읽기 작업이 쓰기 작업보다 훨씬 더 빈번한 데이터 구조에 유용합니다.

std::make_unique
C++11에서는 std::make_shared만 제공되었지만, C++14에서는 std::make_unique도 추가되었습니다.

 

// C++11에서는 직접 new를 사용해야 했습니다
std::unique_ptr<int> p_old(new int(42));

// C++14에서는 make_unique를 사용할 수 있습니다
auto p_new = std::make_unique<int>(42);

// 배열도 지원합니다
auto p_array = std::make_unique<int[]>(10); // 크기가 10인 int 배열

 

이 함수는 메모리 누수없이 예외 안전성을 개선합니다.

 

내장 타입을 위한 정수 시퀀스(integer_sequence)

컴파일 시간 정수 시퀀스를 생성할 수 있는 유틸리티가 추가되었습니다.

 

#include <utility>
#include <tuple>
#include <iostream>

template<typename T, T... ints>
void print_sequence(std::integer_sequence<T, ints...>) {
    ((std::cout << ints << ' '), ...);
    std::cout << '\n';
}

// 튜플 요소에 인덱스를 통해 접근하는 예제
template<class Tuple, std::size_t... Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) {
    ((std::cout << std::get<Is>(t) << ' '), ...);
}

template<class Tuple>
void print_tuple(const Tuple& t) {
    print_tuple_impl(t, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}

int main() {
    // 0부터 4까지의 시퀀스 생성
    print_sequence(std::make_index_sequence<5>{}); // 출력: 0 1 2 3 4

    // 튜플 출력
    auto t = std::make_tuple(10, "hello", 3.14);
    print_tuple(t); // 출력: 10 hello 3.14
}

 

이 기능은 가변 인자 템플릿과 함께 튜플 요소에 접근하거나 패턴 매칭과 같은 고급 메타 프로그래밍 기법에 유용합니다.

 

바이너리 리터럴(Binary Literals)

이전에는 10진수, 8진수, 16진수 리터럴만 지원했지만, C++14에서는 2진수 리터럴도 추가되었습니다.

 

// 2진수 리터럴
int binary = 0b101010;    // 42
int binary_long = 0B1010'1010; // 분리자(separator)도 사용 가능

 

숫자 분리자(Digit Separators)

가독성을 높이기 위해 숫자 리터럴에 분리자(')를 사용할 수 있게 되었습니다.

// 숫자 분리자 사용
int million = 1'000'000;
double pi = 3.141'592'653'589'793;
long hex = 0xDEAD'BEEF;
long binary = 0b1101'0101'0001'1010;

 

반환 타입 추론(Return Type Deduction)

함수의 반환 타입을 auto로 지정하여 컴파일러가 추론하도록 할 수 있습니다.

// 함수 반환 타입 추론
auto add(int x, int y) {
    return x + y;  // int로 추론됨
}

auto get_name() {
    return "John Doe";  // const char*로 추론됨
}

// 재귀 함수에서는 최소한 한 번의 반환문이 필요합니다
auto factorial(int n) -> decltype(auto) {
    if (n <= 1) return 1;  // 여기서 타입이 결정됨
    return n * factorial(n - 1);
}

 

타입을 유지하는 decltype(auto)

C++14에서는 decltype(auto)가 추가되어 참조 타입과 cv-한정자(const, volatile)를 보존할 수 있게 되었습니다.

// decltype(auto) 예제
int x = 42;
const int& cx = x;

auto a = cx;          // int (참조와 const가 제거됨)
decltype(auto) b = cx; // const int& (참조와 const가 유지됨)

// 함수에서 사용
decltype(auto) forward_value(int& x) {
    return x;  // int& 반환
}

decltype(auto) forward_value(const int& x) {
    return x;  // const int& 반환
}

'CS' 카테고리의 다른 글

C++17 표준  (0) 2026.03.06
객체지향 SOLID 원칙 5개  (0) 2026.03.03
C++11 표준  (0) 2026.03.03
C++23: The Next C++ Standard  (0) 2026.03.03
정렬 알고리즘  (0) 2026.03.01

1. SRP - 단일 책임 원칙(Single Responsibility Principle)

정의: 클래스는 단 하나의 변경 이유만 가져야 한다.

나쁜 예

class Player
{
public:
    void Move();
    void SaveToFile();
    void RenderUI();
};

문제:

- 이동 로직 변경 -> 수정

- 저장 방식 변경 -> 수정

- UI 변경 -> 수정

좋은 예

class PlayerMovement {};
class PlayerSaveSystem {};
class PlayerUI {};

각각 책임 분리

언리얼에서는 액터 컴포넌트 구조 자체가 SRP 철학을 가지고 있으며, 저 또한 그 철학 유지하며

 

class FF_API UEquipmentSystem : public UActorComponent    : 장착 역할을 가진 컴포넌트
class FF_API UInventorySystem : public UActorComponent    : 인벤토리 역할을 가진 컴포넌트
class FF_API ULootingSystem : public UActorComponent    : 루팅 역할을 가진 컴포넌트

이 같은 구성을 유지하고 있습니다.

 

2. OCP - 개방 폐쇄 원칙(Open-Closed Principle)

정의: 확장에는 열려 있고, 수정에는 닫혀 있어야 한다.

나쁜 예

int CalcDamage(int type)
{
    if(type == 0) ...
    else if(type == 1) ...
}

몹 추가할 때 마다 수정

좋은 예

class DamagePolicy
{
public:
    virtual int Calc() = 0;
};

class FireDamage : public DamagePolicy {};
class IceDamage : public DamagePolicy {};

새 타입 추가 = 클래스 추가만

언리얼에서는 DataAsset, DataTable 클래스를 활용하여 Data-Driven 기반으로

새로운 무기/스킬/AI(Behavior Tree) 등을 관리할 수 있습니다.

여기서 switch / if 문이 계속 늘어나면 OCP 원칙에 위반할 가능성이 높아진다고 하네요.

 

3. LSP - 리스코프 치환 원칙(Liskov Substitutaion Principle)

자식 클래스는 부모를 완전히 대체할 수 있어야 한다.

나쁜 예

class Bird {
    virtual void Fly();
};

class Penguin : public Bird {
    void Fly() override { throw; }
};

좋은 예

class Bird {};
class FlyingBird : public Bird {};

주로 Weapon 계열을 구현할 때, 총기에만 사용하는 Fire를

냉병기나 방패 같은 클래스도 상속을 받으면 구조가 잘못됐다고 합니다.

최근에 저도 TPS 기준으로 개발하면서 Fire로 지은 함수를 일반적인 Attack으로 리팩토링 한 기억이 남습니다.

중요한 건 이 객체를 부모 타입으로 써도 문제 없는지 살펴보는 감각이라고 하네요.

 

4. ISP - 인터페이스 분리 원칙(Interface Segregation Principle)

정의: 클라이언트는 사용하지 않는 인터페이스에 의존하면 안 된다.

나쁜 예

class ICharacter
{
    virtual void Fly();
    virtual void Swim();
    virtual void Shoot();
};

이렇게 되면 가만히 서있는 NPC가 있을 경우 빈 함수로 내버려둬야 할 것입니다.

좋은 예

class IFlyable {};
class ISwimmable {};
class IShootable {};

필요한 것만 구현하기 위해서 인터페이스는 작고 구체적일수록 좋으며,

저도 인터페이스가 편하긴 한데 추가할 때 신중한 편인 것 같습니다.

 

5. DIP - 의존성 역전 원칙(Dependency Inversion Principle)

정의: 상위 모듈은 하위 모듈에 의존하면 안 된다.

둘 다 추상화에 의존해야 한다.

나쁜 예

class Game
{
    SteamNetwork net;
};

여기서 Steam이 아닌 PSN으로 바꾸면 게임 전체를 수정하게 됩니다.

좋은 예

class INetwork {};

class Game
{
    INetwork* net;
};


class SteamNetwork : public INetwork {};
class PSNNetwork : public INetwork {};

플랫폼/서버/입력 시스템을 변경할 때 이 구조를 가지는게 중요합니다

요약

*SRP: 한 클래스 = 한 역할

*OCP: 수정하지 말고 확장하라

*LSP: 자식은 부모처럼 동작해야

*ISP: 인터페이스는 작게 쪼개라

*DIP: 구현이 아니라 추상에 의존

 

+ 유니티에서 좋은 영상이 올라와 추가

https://youtu.be/e9JfXuRcwkM?si=YBjQ3BOHk25RNVQ4

 

'CS' 카테고리의 다른 글

C++17 표준  (0) 2026.03.06
C++14 표준  (1) 2026.03.04
C++11 표준  (0) 2026.03.03
C++23: The Next C++ Standard  (0) 2026.03.03
정렬 알고리즘  (0) 2026.03.01

C++11 주요 기능 요약

  • 자동 타입 추론(Auto)

    • 변수 타입 자동 추론으로 코드 간결성 향상
    • 복잡한 반복자 타입 직접 명시 불필요
    • 후행 반환 타입으로 함수 선언으로 가독성 개선
  • 람다 표현식

    • 익명 함수를 통한 코드 간결성 및 가독성 향상
    • 외부 변수 캡처 매커니즘 (값, 참조)
    • 알고리즘 함수에 간편하게 동작 전달 가능
  • 이동 의미론과 우측값 참조

    • 불필요한 복사 제거로 성능 향상
    • 리소스 소유권 명시적 이동
    • 완벽한 전달(Perfect Forwarding) 지원
  • 메모리 관리 개선

    • 스마트 포인터(unique_ptr, shared_ptr, weak_ptr)
    • 자동 메모리 관리 및 리소스 누수 방지
    • 참조 카운팅 기반 공유 소유권
  • 병렬 프로그래밍 지원

    • 표준 스레딩 라이브러리
    • 동기화 프리미티브(뮤텍스, 조건 변수)
    • 비동기 작업 지원(future, primise)

1. 자동 타입 추론 (Auto)

*변수, 함수 반환 타입의 자동 추론이 가능해졌습니다.

// 변수의 타입 추론
auto i = 42;               // int
auto d = 3.14;             // double
auto s = "Hello";          // const char*
auto v = std::vector<int>{1, 2, 3}; // std::vector<int>

// 반복자 코드 간소화
std::vector<int> vec = {1, 2, 3};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    // 기존: std::vector<int>::iterator it = vec.begin();
    // ...
}

// 함수 반환 타입 추론 (후행 반환 타입 구문)
auto add(int x, int y) -> int {
    return x + y;
}

2. 범위 기반 for 루프

*컨테이너를 더 쉽게 순회할 수 있는 문법이 추가되었습니다.

std::vector<int> vec = {1, 2, 3, 4, 5};

// 기존 방식
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << ' ';
}

// 범위 기반 for 루프
for (int& value : vec) {
    std::cout << value << ' ';
    value *= 2; // 원본을 수정할 수 있음
}

// 읽기 전용으로 접근
for (const auto& value : vec) {
    std::cout << value << ' ';
}

3. 람다 표현식

*익명 함수를 만들 수 있는 람다 표현식이 도입되었습니다.

// 기본 람다 문법
auto greet = []() { std::cout << "Hello, World!\n"; };
greet(); // "Hello, World!" 출력

// 매개변수를 받는 람다
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4); // 7 출력

// 캡처 목록을 사용하는 람다
int multiplier = 2;
auto multiply = [multiplier](int x) { return x * multiplier; };
std::cout << multiply(5); // 10 출력

// 값 변경이 가능한 캡처 목록
auto modify = [multiplier]() mutable { 
    multiplier *= 2; 
    return multiplier; 
};

// 모든 변수를 참조로 캡처
[&]() { /* 외부 변수를 참조로 접근 */ };

// 모든 변수를 값으로 캡처
[=]() { /* 외부 변수를 값으로 접근 */ };

// 일부 변수만 캡처
[x, &y]() { /* x는 값으로, y는 참조로 캡처 */ };

4. 이동 의미론(Move Semantics)과 우측값 참조(Rvalue References)

**객체의 소유권을 효율적으로 이동할 수 있는 매커니즘이 추가되었습니다.

// 이동 생성자와 이동 대입 연산자
class MyString {
private:
    char* data;
    size_t length;

public:
    // 이동 생성자
    MyString(MyString&& other) noexcept 
        : data(other.data), length(other.length) {
        other.data = nullptr;
        other.length = 0;
    }

    // 이동 대입 연산자
    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            length = other.length;
            other.data = nullptr;
            other.length = 0;
        }
        return *this;
    }

    // ... 다른 멤버들 ...
};

// 사용 예
MyString createString() {
    MyString temp("Hello");
    return temp; // 이동 생성자를 통해 효율적으로 이동됨
}

MyString s = createString(); // 불필요한 복사 없음

5. 스마트 포인터

*메모리 관리를 자동화하는 스마트 포인터가 표준 라이브러리에 추가되었습니다.

#include <memory>

// unique_ptr: 단독 소유권
std::unique_ptr<int> p1(new int(42));
//std::unique_ptr<int> p2 = p1; // 컴파일 오류: 복사 불가
std::unique_ptr<int> p3 = std::move(p1); // 이동은 가능

// shared_ptr: 공유 소유권
std::shared_ptr<int> sp1(new int(42));
std::shared_ptr<int> sp2 = sp1; // 참조 카운트 증가
std::cout << *sp1 << ", " << *sp2 << ", Count: " << sp1.use_count(); // 42, 42, Count: 2

// weak_ptr: 약한 참조
std::weak_ptr<int> wp = sp1;
if (auto locked = wp.lock()) {
    std::cout << *locked; // 42
}

// make_shared: 더 효율적인 shared_ptr 생성
auto sp3 = std::make_shared<int>(42);

6. 타입 추론(Decltype)

*표현식의 타입을 추론할 수 있는 decltype 키워드가 도입되었습니다.

int i = 42;
double d = 3.14;
const int& r = i;

decltype(i) x = i;         // int
decltype(d) y = d;         // double
decltype(r) z = i;         // const int&
decltype(i + d) w = i + d; // double

// 함수 반환 타입으로도 사용 가능
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
    return t + u;
}

7. 초기화 리스트 (Initializer Lists)

*객체 초기화를 위한 일관된 문법이 도입되었습니다.

// 중괄호 초기화
int a{42};
double b{3.14};
bool c{true};

// 배열 초기화
int arr[] = {1, 2, 3, 4, 5};

// 벡터 초기화
std::vector<int> vec = {1, 2, 3, 4, 5};

// 맵 초기화
std::map<std::string, int> ages = {
    {"Alice", 25},
    {"Bob", 30},
    {"Charlie", 35}
};

// 클래스에서의 초기화 리스트 지원
class Widget {
public:
    Widget(std::initializer_list<int> values) {
        // values 처리
    }
};

Widget w = {1, 2, 3, 4, 5};

8.nullptr

**NULL0을 대체하는 타입 안전한 널 포인터 상수가 도입되었습니다.

// nullptr 사용
int* p = nullptr;  // 기존: int* p = NULL; 또는 int* p = 0;

// 함수 오버로딩에서 더 명확해짐
void f(int);
void f(char*);

f(0);      // f(int) 호출
f(nullptr); // f(char*) 호출

9. 열거형 클래스 (Enum Classes)

*타입 안전성이 향상된 열거형이 추가되었습니다.

// 기존 열거형의 문제점
enum Color { RED, GREEN, BLUE };
enum Fruit { APPLE, BANANA, ORANGE };

Color c = RED;
if (c == APPLE) { // 서로 다른 열거형이지만 비교 가능 (컴파일 에러 없음)
    // ...
}

// 열거형 클래스
enum class NewColor { RED, GREEN, BLUE };
enum class NewFruit { APPLE, BANANA, ORANGE };

NewColor nc = NewColor::RED;
//if (nc == NewFruit::APPLE) { // 컴파일 오류: 서로 다른 타입 비교 불가
//}

// 또한 명시적으로 타입 지정 가능
enum class Flags : uint8_t {
    OPTION1 = 0x01,
    OPTION2 = 0x02,
    OPTION3 = 0x04
};

10. constexpr

**컴파일 시간에 평가될 수 있는 표현식을 정의할 수 있는 키워드가 도입되었습니다.

// 컴파일 시간 상수
constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

// 컴파일 시간에 계산됨
constexpr int fact5 = factorial(5); // 120

// 컴파일 시간에 평가되는 변수
constexpr double PI = 3.14159265358979;
constexpr double TWICE_PI = 2.0 * PI;

11. 쓰레딩 라이브러리

*멀티쓰레딩 프로그래밍을 위한 표준 라이브러리가 추가되었습니다.

#include <thread>
#include <mutex>
#include <future>
#include <chrono>

// 기본 쓰레드 사용
std::thread t1([]() {
    std::cout << "Hello from thread!" << std::endl;
});
t1.join();

// 뮤텍스를 사용한 동기화
std::mutex mtx;
int counter = 0;

void increment() {
    std::lock_guard<std::mutex> lock(mtx);
    ++counter;
}

// Future와 Promise를 사용한 비동기 작업
std::future<int> compute = std::async(std::launch::async, []() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
});

// 결과 받기
int result = compute.get(); // 1초 후 42를 반환

12. 가변 인자 템플릿 (Variadic Templates)

*임의의 개수와 타입의 매개변수를 받을 수 있는 템플릿이 가능해졌습니다.

// 가변 인자 템플릿 함수
template<typename... Args>
void printAll(Args... args) {
    (std::cout << ... << args) << std::endl;
}

printAll(1, 2.5, "Hello", 'x'); // "12.5Hellox" 출력

// 재귀적 구현 (C++17 이전)
void print() {
    std::cout << std::endl;
}

template<typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << ' ';
    print(rest...); // 나머지 인수로 재귀 호출
}

print(1, 2.5, "Hello", 'x'); // "1 2.5 Hello x" 출력

// 튜플 구현에도 사용됨
template<typename... Types>
class tuple;

13. 사용자 정의 리터럴

*사용자가 리터럴 표기법을 확장할 수 있는 기능이 추가되었습니다.

// 사용자 정의 리터럴
constexpr long double operator""_km(long double value) {
    return value * 1000.0; // 킬로미터를 미터로 변환
}

constexpr long double operator""_mi(long double value) {
    return value * 1609.344; // 마일을 미터로 변환
}

// 사용 예
auto marathon = 42.195_km; // 42195.0 미터
auto mile = 1.0_mi;        // 1609.344 미터

14. 명시적 오버라이드와 final

**상속 관계에서 메서드 오버라이딩을 명시적으로 표현할 수 있게 되었습니다.

class Base {
public:
    virtual void foo();
    virtual void bar();
    virtual void baz();
};

class Derived : public Base {
public:
    void foo() override; // 명시적으로 오버라이드 표시
    void bar() final;    // 하위 클래스에서 더 이상 오버라이드할 수 없음
    //void qux() override; // 컴파일 오류: Base에 qux가 없음
};

class FurtherDerived : public Derived {
public:
    //void bar() override; // 컴파일 오류: bar는 final
};

final class SealedClass { /* ... */ }; // 이 클래스는 상속될 수 없음

15. 멤버 함수 참조 한정자(Ref-qualfiers)

*멤버 함수가 lvalue 또는 rvalue에서만 호출될 수 있도록 지정할 수 있습니다.

class Widget {
public:
    void doWork() &;  // lvalue (*this)에서만 호출 가능
    void doWork() &&; // rvalue (*this)에서만 호출 가능
};

Widget w;
w.doWork();      // 첫 번째 버전 호출 (w는 lvalue)
Widget().doWork(); // 두 번째 버전 호출 (Widget()은 rvalue)

메모리 모델과 쓰레딩

C++11은 멀티스레드 환경에서의 동작을 정의하는 새로운 메모리 모델을 도입했습니다. 이는 현대적인 CPU 아키텍쳐와 병렬 프로그래밍을 위한 기법이 됩니다.

#include <atomic>

// 원자적 연산
std::atomic<int> counter(0);
counter.fetch_add(1, std::memory_order_relaxed);

// 메모리 순서 지정
std::atomic<bool> ready(false);
std::atomic<int> data(0);

// 쓰기 스레드
data.store(42, std::memory_order_release);
ready.store(true, std::memory_order_release);

// 읽기 스레드
while (!ready.load(std::memory_order_acquire));
int answer = data.load(std::memory_order_acquire);

좌측값(lvalue)과 우측값(rvalue)

C++에서 표현식은 두 가지 주요 카테고리로 구분됩니다:

  • 좌측값(lvalue)과 우측값(rvalue)
  • 이 구분은 C++11에서 도입된 이동 의미론(Move Semantics)과 완벽한 전달(Perfect Forwarding)의 기반이 됩니다.

좌측값(lvalue)

좌측값은 다음과 같은 특성을 갖습니다:

  • 이름이 있고 주소를 가질 수 있음
  • 대입 연산자 왼쪽에 올 수 있음
  • 참조 수명이 장기적임
    예시:
    int x = 10;          // x는 좌측값
    int& ref = x;        // 좌측값 참조로 x를 참조
    int* ptr = &x;       // 좌측값의 주소를 얻을 수 있음
    x = 20;              // 좌측값에 값 할당 가능

우측값(rvalue)

우측값은 다음과 같은 특성을 갖습니다:

  • 이름이 없음(임시 값)
  • 대입 연산자 왼쪽에 올 수 없음
  • 표현식 이후 곧 소멸됨(임시적)
  • 이동 가능함(move semantics 대상)

예시:

int x = 10 + 20;     // 10 + 20은 우측값
int&& rref = 5 + 3;  // 우측값 참조
//5 = x;             // 오류: 우측값에 할당 불가

우측값에는 다음이 포함됩니다:

  • 리터럴(10, 3.14, true 등)
  • 임시 객체
  • 비참조 값을 반환하는 함수 호출
  • 산술 표현식(a + b, x * y 등)
  • 후위 증감 연산자(x++)

값 카테고리의 계층 구조

C++11에서는 값 카테고리가 더 세분화되었습니다:

표현식(Expression)
├── 좌측값(lvalue)
├── 우측값(rvalue)
    ├── 소멸 값(xvalue, eXpiring value)
    └── 순수 우측값(prvalue, pure rvalue)
  • 소멸 값(xvalue): 이동될 수 있는 리소스를 가리키는 표현식 (예: std::move(x))
  • 순수 우측값(prvalue): 임시 값, 리터럴 등

우측값 참조

C++11에서 도입된 우측값 참조(&&)는 우측값에 바인딩할 수 있는 새로운 참조 타입입니다:

int&& rref = 42;        // 우측값 참조
int&& rref2 = x + y;    // 계산 결과인 우측값에 바인딩

// 함수 오버로딩
void foo(int& x) {       // 좌측값 참조 버전
    std::cout << "lvalue 참조\n";
}
void foo(int&& x) {      // 우측값 참조 버전
    std::cout << "rvalue 참조\n";
}

int main() {
    int a = 10;
    foo(a);         // "lvalue 참조" 출력
    foo(10);        // "rvalue 참조" 출력
    foo(a + 10);    // "rvalue 참조" 출력
}

완벽한 전달(Perfect Forwarding)

C++11에서는 우측값 참조(&&)와 std::forward를 통해 함수가 받은 인자를 다른 함수에 원래의 값 카테고리(lvalue/rvalue)를 그대로 유지하며 전달할 수 있는 완벽한 전달 메커니즘을 제공합니다.

#include <utility>
#include <iostream>

// 다양한 인자를 받는 목표 함수
void process(int& x) {
    std::cout << "좌측값 참조: " << x << '\n';
    ++x;
}

void process(int&& x) {
    std::cout << "우측값 참조: " << x << '\n';
}

// 완벽한 전달을 구현한 래퍼 함수
template<typename T>
void wrapper(T&& arg) {
    // std::forward를 사용하여 원래 값 카테고리 보존
    process(std::forward<T>(arg));
}

int main() {
    int a = 42;

    process(a);        // 좌측값 버전 호출
    process(123);      // 우측값 버전 호출

    wrapper(a);        // T는 int&, arg는 int&로 추론되고 좌측값으로 전달됨
    wrapper(123);      // T는 int, arg는 int&&로 추론되고 우측값으로 전달됨

    std::cout << "a 값: " << a << '\n';  // a가 43으로 증가함

    return 0;
}

완벽한 전달의 작동 원리

  1. 유니버설 참조(Universal Reference):

    • 템플릿 타입 매개변수 T와 함께 사용된 T&&는 우측값 참조가 아닌 유니버설 참조입니다.
    • 좌측값이 전달되면 TType&로 추론되고, T&&Type&가 됩니다.
    • 우측값이 전달되면 TType으로 추론되고, T&&Type&&가 됩니다.
  2. 참조 접기(Reference Collapsing):

    • Type& &&Type& (좌측값 참조)
    • Type&& &&Type&& (우측값 참조)
  3. std::forward의 역할:

    • 좌측값이 전달되었다면 좌측값으로 유지
    • 우측값이 전달되었다면 우측값으로 변환
    • 이를 통해 원래의 값 카테고리를 보존

완벽한 전달의 장점

  • 여러 오버로드 함수를 위한 래퍼 함수를 하나만 작성하면 됨
  • 불필요한 복사 없이 효율적인 인자 전달
  • 생성자와 팩토리 함수에서 특히 유용
  • std::make_shared, std::make_unique 등이 내부적으로 이 기술을 활용

이 기능은 특히 가변 인자 템플릿과 함께 사용될 때 강력하며, 모던 C++의 성능과 표현력 향상에 크게 기여합니다.

'CS' 카테고리의 다른 글

C++17 표준  (0) 2026.03.06
C++14 표준  (1) 2026.03.04
객체지향 SOLID 원칙 5개  (0) 2026.03.03
C++23: The Next C++ Standard  (0) 2026.03.03
정렬 알고리즘  (0) 2026.03.01

C++ 표준의 역사

C++98

  • 80년대 말에 Bjarne Stroustrup과 Margaret A. Ellis가 유명한 "Annotated C++ Reference Manual (ARM)" 책을 저술
  • 이 책의 목적은 두 가지:
    • 너무 많은 C++ 독립 구현체들이 있었기 때문에 ARM은 C++의 기능을 정의
    • C++ 표준인 C++98 (ISO/IEC 14882)의 기초가 됨
  • C++98은 몇몇 중요한 기능들을 포함:
    • Templates
    • STL(Standard Template Library): 컨테이너, 알고리즘, 스트링, IO Stream들을 포함

C++03

  • C++03 (14882:2003)에서는 아주 작은 기술 수정이 이뤄짐
  • 커뮤니티에서는 C++98을 포함한 C++03을 "레거시 C++"이라고 부름

TR1

  • 2005년에 재미난 일이 생김. TR1(Technical Report 1)이 발간
  • TR1은 C++11을 위한 큰 발걸음이자 Modern C++로 향하는 첫걸음
  • C++ 표준 위원회 멤버들의 Boost 프로젝트에 기반
  • 차세대 C++ 표준에 들어갈 13개의 라이브러리를 포함:
    • 정규식
    • 난수
    • std::shared_ptr 같은 스마트 포인터
    • 해시테이블 등

C++11

  • 다음 C++ 표준이었지만, 우린 Modern C++이라고 부름 (이 이름엔 C++14와 C++17을 포함)
  • C++11은 C++을 개발하는 방식을 완전히 바꿔버림:
    • TR1의 컴포넌트
    • move-semantic
    • perfect forwarding
    • variadic templates
    • constexpr
    • 스레딩 기반의 메모리 모델과 Threading API

C++14

  • 작은 C++ 표준
  • 주요 기능:
    • read-writer locks
    • generalized lambdas
    • generalized constexpr 함수

C++17

  • 크지도 작지도 않은 표준
  • 두 가지 뛰어난 기능:
    • Parallel STL
    • 표준화된 파일시스템
  • 약 80개의 STL 알고리즘이 실행 정책을 통해 실행 가능:
    • 병렬
    • 순차
    • 벡터
  • Boost에서 많은 영향을 받음:
    • 파일 시스템
    • 3개의 새로운 데이터 타입:
      • std::optional
      • std::variant
      • std::any

C++20

C++20은 C++11과 마찬가지로 C++ 프로그래밍 방식을 바꿔버림. 특히 다음 네 가지 큰 기능이 있습니다:

    • 컨테이너에서 직접 알고리즘을 표현
    • 파이프 기호로 알고리즘을 조합
    • 무한 데이터 스트림에 적용 가능
    • 지연 평가(Lazy Evaluation): 실제로 값이 필요할 때까지 연산을 미룸
    1. 메모리 효율성
      • 중간 결과를 저장하지 않고 필요할 때만 계산
      • 예: filter와 transform을 체이닝할 때 중간 벡터를 생성하지 않음
    2. 무한 시퀀스 처리 가능
      • 전체 시퀀스를 메모리에 저장하지 않고도 처리 가능
      • take를 사용하여 필요한 만큼만 계산
    3. 성능 최적화
      • 불필요한 연산을 피할 수 있음
      • 예: take(3) 이후의 요소는 계산하지 않음
    4. 파이프라인 최적화
      • 컴파일러가 전체 파이프라인을 최적화할 수 있음
      • 여러 연산을 하나의 루프로 결합 가능
  1. 지연 평가의 장점
    • C++에서 비동기 프로그래밍이 주류가 되게 함
    • 협동작업, 이벤트 루프, 무한 데이터 스트림 및 파이프라인의 기반
    1. 비동기 작업 처리
      • co_await: 비동기 작업의 완료를 기다림
      • co_yield: 값을 반환하고 실행을 일시 중단
      • co_return: 코루틴 종료
    2. 상태 보존
      • 코루틴은 실행 상태를 보존
      • 재개 시 이전 상태에서 계속 실행
    3. 메모리 효율성
      • 스택 대신 힙에 상태 저장
      • 많은 수의 동시 작업 처리 가능
    4. 파이프라인 구성
      • 여러 코루틴을 연결하여 복잡한 비동기 파이프라인 구성 가능
      • 데이터 스트림 처리에 적합
  2. 코루틴의 주요 특징
    • 템플릿을 생각하고 프로그래밍하는 방식을 바꿈
    • 템플릿 인자에 대한 제약과 타입 검사
    • 컴파일 시점에 확인 가능
    1. 타입 제약
      • 템플릿 매개변수에 대한 명확한 요구사항 정의
      • 컴파일 시점에 타입 검사 가능
    2. 코드 가독성
      • 템플릿 코드의 의도를 명확하게 표현
      • 오류 메시지가 더 명확하고 이해하기 쉬움
    3. 재사용성
      • 개념을 조합하여 새로운 제약 조건 생성 가능
      • 표준 라이브러리와의 통합 용이
  3. Concepts의 주요 특징
    • 헤더 파일의 한계를 극복
    • 전처리기 불필요
    • 빌드 시간 단축
    • 패키지 빌드 용이성 향상
    1. 빌드 성능
      • 헤더 파일 중복 포함 문제 해결
      • 컴파일 시간 단축
      • 의존성 관리 용이
    2. 캡슐화
      • 모듈 내부 구현 세부사항 숨김
      • 명시적인 인터페이스 정의
      • 더 나은 코드 구조화
    3. 패키지 관리
      • 모듈 단위의 코드 재사용
      • 의존성 명확화
      • 빌드 시스템 통합 용이
  4. Modules의 주요 특징

C++23

  • 2023년 7월 현재 C++23이 최종 투표를 앞두고 있음

핵심 언어 기능

    • 작지만 매우 영향력 있는 기능
    • Python과 유사하게, 멤버함수에 implicit하게 전달된 this 포인터를 explicit하게 만듦
    • CRTP(Curiously Recurring Template Pattern)나 Overload 패턴 구현이 간단해짐
    1. 코드 가독성 향상
      • 함수의 동작과 의도를 더 명확하게 표현
      • 일관된 문법으로 멤버 함수와 일반 함수의 차이 감소
    2. 더 적은 템플릿 상용구
      • CRTP 패턴 단순화
      • 다양한 참조 한정자(reference qualifiers)를 위한 중복 코드 감소
    3. 강력한 인터페이스 설계
      • 다형성 없이도 공통 인터페이스 구현 가능
      • 런타임 다형성의 오버헤드 없이 정적 다형성 구현
    4. 완벽한 전달(Perfect forwarding) 지원
      • 레퍼런스 카테고리(lvalue/rvalue) 보존
      • 효율적인 값 전달 메커니즘
  • "Deducing this"의 주요 혜택

라이브러리 기능

  • 표준 라이브러리 개선
    • import std;로 표준 라이브러리 직접 임포트
    • std::print  std::println에서 C++20 포맷 스트링 적용 가능
    • std::flat_map: 성능 향상을 위한 std::map 대체제
    • std::optional: Monadic 인터페이스로 확장되어 Composability 향상
    • std::expected: 새로운 데이터 타입
    • std::mdspan: 다차원 span
    • std::generator: 숫자들의 스트림을 생성하기 위한 코루틴

 

출처: https://www.modernescpp.com/index.php/c23-the-next-c-standard/

'CS' 카테고리의 다른 글

C++17 표준  (0) 2026.03.06
C++14 표준  (1) 2026.03.04
객체지향 SOLID 원칙 5개  (0) 2026.03.03
C++11 표준  (0) 2026.03.03
정렬 알고리즘  (0) 2026.03.01
// 1. 버블 정렬 (Bubble Sort)
// 인접한 두 원소를 비교해 더 큰 값을 오른쪽으로 밀어냄
// 시간복잡도: O(n²), 공간복잡도: O(1)
void bubbleSort(int arr[], int length)
{
    for (int i = 0; i < length - 1; i++)
    {
        bool swapped = false;
        for (int j = 0; j < length - 1 - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
        if (!swapped) break; // 이미 정렬된 경우 조기 종료
    }
}
 
// 2. 선택 정렬 (Selection Sort)
// 미정렬 구간에서 최솟값을 찾아 맨 앞으로 이동
// 시간복잡도: O(n²), 공간복잡도: O(1)
void selectionSort(int arr[], int length)
{
    for (int i = 0; i < length - 1; i++)
    {
        int minIdx = i;
        for (int j = i + 1; j < length; j++)
        {
            if (arr[j] < arr[minIdx])
                minIdx = j;
        }
        swap(arr[i], arr[minIdx]);
    }
}
 
// 3. 삽입 정렬 (Insertion Sort)
// 현재 원소를 정렬된 구간의 올바른 위치에 삽입
// 시간복잡도: O(n²), 거의 정렬된 경우 O(n), 공간복잡도: O(1)
void insertionSort(int arr[], int length)
{
    for (int i = 1; i < length; i++)
    {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key)
        {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}
 
// 4. 합병 정렬 (Merge Sort)
// 배열을 반으로 나눠 재귀적으로 정렬한 후 합병
// 시간복잡도: O(n log n), 공간복잡도: O(n)
void merge(int arr[], int left, int mid, int right)
{
    int n1 = mid - left + 1;
    int n2 = right - mid;

    int* L = new int[n1];
    int* R = new int[n2];

    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int i = 0; i < n2; i++) R[i] = arr[mid + 1 + i];

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2)
    {
        if (L[i] <= R[j]) arr[k++] = L[i++];
        else               arr[k++] = R[j++];
    }
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];

    delete[] L;
    delete[] R;
}

void mergeSort(int arr[], int left, int right)
{
    if (left >= right) return;
    int mid = left + (right - left) / 2;
    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);
    merge(arr, left, mid, right);
}
 
// 5. 퀵 정렬 (Quick Sort)
// 피벗을 기준으로 작은 값은 왼쪽, 큰 값은 오른쪽으로 분할 후 재귀 정렬
// 시간복잡도: 평균 O(n log n), 최악 O(n²), 공간복잡도: O(log n)
int partition(int arr[], int left, int right)
{
    int pivot = arr[right];
    int i = left - 1;

    for (int j = left; j < right; j++)
    {
        if (arr[j] <= pivot)
        {
            i++;
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[right]);
    return i + 1;
}

void quickSort(int arr[], int left, int right)
{
    if (left >= right) return;
    int pivotIdx = partition(arr, left, right);
    quickSort(arr, left, pivotIdx - 1);
    quickSort(arr, pivotIdx + 1, right);
}
 
// 6. 힙 정렬 (Heap Sort)
// 최대 힙을 구성한 후 루트(최댓값)를 꺼내며 정렬
// 시간복잡도: O(n log n), 공간복잡도: O(1)
void heapify(int arr[], int length, int i)
{
    int largest = i;
    int left    = 2 * i + 1;
    int right   = 2 * i + 2;

    if (left  < length && arr[left]  > arr[largest]) largest = left;
    if (right < length && arr[right] > arr[largest]) largest = right;

    if (largest != i)
    {
        swap(arr[i], arr[largest]);
        heapify(arr, length, largest);
    }
}

void heapSort(int arr[], int length)
{
    for (int i = length / 2 - 1; i >= 0; i--)
        heapify(arr, length, i);

    for (int i = length - 1; i > 0; i--)
    {
        swap(arr[0], arr[i]);
        heapify(arr, i, 0);
    }
}
 
// 7. 계수 정렬 (Counting Sort)
// 각 값의 등장 횟수를 세어 정렬 (정수, 값의 범위가 작을 때 유리)
// 시간복잡도: O(n + k), 공간복잡도: O(k)  [k = 값의 범위]
void countingSort(int arr[], int length)
{
    int maxVal = arr[0];
    for (int i = 1; i < length; i++)
        if (arr[i] > maxVal) maxVal = arr[i];

    int* count = new int[maxVal + 1]();
    for (int i = 0; i < length; i++)
        count[arr[i]]++;

    int idx = 0;
    for (int i = 0; i <= maxVal; i++)
        while (count[i]-- > 0)
            arr[idx++] = i;

    delete[] count;
}
 

'CS' 카테고리의 다른 글

C++17 표준  (0) 2026.03.06
C++14 표준  (1) 2026.03.04
객체지향 SOLID 원칙 5개  (0) 2026.03.03
C++11 표준  (0) 2026.03.03
C++23: The Next C++ Standard  (0) 2026.03.03

+ Recent posts