C++ Chrono Literals | "시간 리터럴" 가이드

C++ Chrono Literals | "시간 리터럴" 가이드

이 글의 핵심

C++ Chrono Literals에 대한 실전 가이드입니다.

들어가며

C++14의 chrono 리터럴은 시간 값을 간결하고 읽기 쉽게 표현할 수 있게 해줍니다. std::chrono::milliseconds(500) 대신 500ms로 작성할 수 있습니다.


1. chrono 리터럴 기본

리터럴 종류

#include <iostream>
#include <chrono>

using namespace std::chrono_literals;

int main() {
    // 시간 리터럴
    auto ns = 100ns;     // nanoseconds (나노초)
    auto us = 100us;     // microseconds (마이크로초)
    auto ms = 100ms;     // milliseconds (밀리초)
    auto s = 5s;         // seconds (초)
    auto min = 10min;    // minutes (분)
    auto h = 2h;         // hours (시간)
    
    std::cout << "5초: " << s.count() << "s" << std::endl;
    std::cout << "10분: " << min.count() << "min" << std::endl;
    std::cout << "2시간: " << h.count() << "h" << std::endl;
}

리터럴 타입

리터럴타입설명
100nsstd::chrono::nanoseconds나노초 (10⁻⁹초)
100usstd::chrono::microseconds마이크로초 (10⁻⁶초)
100msstd::chrono::milliseconds밀리초 (10⁻³초)
5sstd::chrono::seconds
10minstd::chrono::minutes
2hstd::chrono::hours시간

핵심 개념:

  • namespace: std::chrono_literals 필수
  • 타입 안전: 컴파일 타임에 타입 체크
  • 가독성: 코드가 명확하고 간결함

2. 시간 연산

기본 연산

#include <iostream>
#include <chrono>

using namespace std::chrono_literals;

int main() {
    // 덧셈
    auto total = 1h + 30min + 45s;
    
    // 뺄셈
    auto diff = 2h - 30min;
    
    // 곱셈
    auto doubled = 5s * 2;
    
    // 나눗셈
    auto half = 10min / 2;
    
    // 비교
    if (500ms < 1s) {
        std::cout << "500ms는 1초보다 짧습니다" << std::endl;
    }
}

단위 변환

#include <iostream>
#include <chrono>

using namespace std::chrono_literals;

int main() {
    auto total = 1h + 30min + 45s;
    
    // 초로 변환
    auto totalSeconds = std::chrono::duration_cast<std::chrono::seconds>(total);
    std::cout << "총 " << totalSeconds.count() << "초" << std::endl;
    // 출력: 총 5445초
    
    // 밀리초로 변환
    auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(total);
    std::cout << "총 " << totalMs.count() << "ms" << std::endl;
    // 출력: 총 5445000ms
    
    // 분으로 변환 (소수점 버림)
    auto totalMin = std::chrono::duration_cast<std::chrono::minutes>(total);
    std::cout << "총 " << totalMin.count() << "분" << std::endl;
    // 출력: 총 90분
}

3. 실전 예제

예제 1: sleep

#include <iostream>
#include <thread>
#include <chrono>

using namespace std::chrono_literals;

int main() {
    std::cout << "시작" << std::endl;
    
    // 기존 방식 (복잡)
    // std::this_thread::sleep_for(std::chrono::seconds(2));
    
    // 리터럴 사용 (간결)
    std::this_thread::sleep_for(2s);
    
    std::cout << "2초 후" << std::endl;
    
    // 밀리초 단위
    std::this_thread::sleep_for(500ms);
    std::cout << "0.5초 후" << std::endl;
}

예제 2: 타임아웃

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

using namespace std::chrono_literals;

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void worker() {
    std::this_thread::sleep_for(1s);
    
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    
    cv.notify_one();
}

int main() {
    std::thread t(worker);
    
    std::unique_lock<std::mutex> lock(mtx);
    
    // 500ms 대기
    if (cv.wait_for(lock, 500ms, []{ return ready; })) {
        std::cout << "준비 완료" << std::endl;
    } else {
        std::cout << "타임아웃 (500ms)" << std::endl;
    }
    
    // 2초 대기
    if (cv.wait_for(lock, 2s, []{ return ready; })) {
        std::cout << "준비 완료" << std::endl;
    }
    
    t.join();
}

예제 3: 타이머 클래스

#include <iostream>
#include <chrono>
#include <thread>

using namespace std::chrono_literals;

class Timer {
    std::chrono::steady_clock::time_point start;
    
public:
    Timer() : start(std::chrono::steady_clock::now()) {}
    
    // 경과 시간 확인
    template<typename Duration>
    bool elapsed(Duration timeout) const {
        auto now = std::chrono::steady_clock::now();
        return now - start >= timeout;
    }
    
    // 경과 시간 반환
    auto getElapsed() const {
        auto now = std::chrono::steady_clock::now();
        return now - start;
    }
    
    // 리셋
    void reset() {
        start = std::chrono::steady_clock::now();
    }
};

int main() {
    Timer timer;
    int count = 0;
    
    // 5초 동안 작업
    while (!timer.elapsed(5s)) {
        std::this_thread::sleep_for(100ms);
        count++;
        
        if (count % 10 == 0) {
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
                timer.getElapsed()
            );
            std::cout << elapsed.count() << "초 경과..." << std::endl;
        }
    }
    
    std::cout << "5초 경과, 총 " << count << "번 실행" << std::endl;
}

4. C++20 날짜 리터럴

날짜 리터럴 사용

#include <iostream>
#include <chrono>

using namespace std::chrono;
using namespace std::chrono_literals;

int main() {
    // 날짜 리터럴 (C++20)
    auto year = 2026y;
    auto day = 29d;
    
    // 날짜 생성
    auto date = 2026y / March / 29d;
    
    std::cout << date << std::endl;
    // 2026-03-29
    
    // 시간과 날짜 결합
    auto datetime = sys_days{2026y / March / 29d} + 14h + 30min;
}

날짜 연산

#include <iostream>
#include <chrono>

using namespace std::chrono;
using namespace std::chrono_literals;

int main() {
    auto today = 2026y / March / 29d;
    
    // 날짜 덧셈
    auto nextWeek = sys_days{today} + days{7};
    auto nextMonth = sys_days{today} + months{1};
    
    // 날짜 차이
    auto date1 = sys_days{2026y / March / 1d};
    auto date2 = sys_days{2026y / March / 29d};
    auto diff = date2 - date1;
    
    std::cout << "차이: " << diff.count() << "일" << std::endl;
    // 차이: 28일
}

5. 자주 발생하는 문제

문제 1: namespace 누락

#include <chrono>

int main() {
    // ❌ namespace 없음
    // auto sec = 5s;  // 컴파일 에러
    
    // ✅ namespace 사용
    using namespace std::chrono_literals;
    auto sec = 5s;
    
    // ✅ 또는 명시적 호출
    auto sec2 = std::chrono_literals::operator""s(5);
}

에러 메시지:

error: unable to find numeric literal operator 'operator""s'

문제 2: 타입 추론

#include <chrono>

using namespace std::chrono_literals;

int main() {
    // auto: 정확한 타입 추론
    auto ms = 100ms;  // std::chrono::milliseconds
    
    // 명시적 타입
    std::chrono::milliseconds ms2 = 100ms;
    
    // 암묵적 변환
    std::chrono::seconds sec = 1000ms;  // 1초
    
    // ❌ 정밀도 손실
    // std::chrono::seconds sec2 = 1500ms;  // 컴파일 에러 (1.5초)
    
    // ✅ duration_cast 사용
    auto sec3 = std::chrono::duration_cast<std::chrono::seconds>(1500ms);
    std::cout << sec3.count() << "초" << std::endl;  // 1초 (버림)
}

문제 3: 오버플로우

#include <chrono>
#include <iostream>

using namespace std::chrono_literals;

int main() {
    // ❌ 큰 값 주의 (오버플로우 가능)
    // auto days = 365 * 24h;  // int 오버플로우
    
    // ✅ 명시적 타입 사용
    auto hours = std::chrono::hours(365 * 24);
    
    // ✅ 또는 리터럴 먼저 사용
    auto oneDay = 24h;
    auto year = oneDay * 365;
    
    auto totalHours = std::chrono::duration_cast<std::chrono::hours>(year);
    std::cout << "1년: " << totalHours.count() << "시간" << std::endl;
}

문제 4: 혼합 연산

#include <chrono>
#include <iostream>

using namespace std::chrono_literals;

int main() {
    // 다른 단위 연산 (자동 변환)
    auto total = 1h + 30min + 45s;
    
    // 가장 작은 단위로 자동 변환됨 (초)
    std::cout << "타입: " << typeid(total).name() << std::endl;
    
    // 명시적 변환
    auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(total);
    std::cout << totalMs.count() << "ms" << std::endl;
    
    // 비교 연산 (자동 변환)
    if (500ms < 1s) {
        std::cout << "500ms는 1초보다 짧습니다" << std::endl;
    }
}

6. 가독성 향상 패턴

기존 방식 vs 리터럴

#include <thread>
#include <chrono>

using namespace std::chrono_literals;

int main() {
    // ❌ 기존 방식 (읽기 어려움)
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::this_thread::sleep_for(std::chrono::minutes(5));
    
    // ✅ 리터럴 사용 (간결하고 명확)
    std::this_thread::sleep_for(500ms);
    std::this_thread::sleep_for(2s);
    std::this_thread::sleep_for(5min);
    
    // ❌ 복잡한 계산
    auto timeout = std::chrono::seconds(60 * 5);
    
    // ✅ 명확한 의도
    auto timeout2 = 5min;
    
    // ✅ 복잡한 시간 표현
    auto duration = 1h + 30min + 45s;
}

실전 활용

#include <iostream>
#include <chrono>
#include <thread>

using namespace std::chrono_literals;

// 재시도 로직
template<typename Func>
bool retryWithTimeout(Func func, std::chrono::milliseconds timeout) {
    auto start = std::chrono::steady_clock::now();
    
    while (true) {
        if (func()) {
            return true;
        }
        
        auto elapsed = std::chrono::steady_clock::now() - start;
        if (elapsed >= timeout) {
            return false;
        }
        
        std::this_thread::sleep_for(100ms);
    }
}

int main() {
    int attempt = 0;
    
    bool success = retryWithTimeout([&]() {
        attempt++;
        std::cout << "시도 " << attempt << std::endl;
        return attempt >= 3;  // 3번째에 성공
    }, 5s);
    
    if (success) {
        std::cout << "성공!" << std::endl;
    } else {
        std::cout << "타임아웃" << std::endl;
    }
}

정리

핵심 요약

  1. 리터럴 종류: ns, us, ms, s, min, h
  2. namespace: std::chrono_literals 필수
  3. 연산: 덧셈, 뺄셈, 곱셈, 나눗셈, 비교
  4. 변환: duration_cast로 단위 변환
  5. C++20: 날짜 리터럴 (y, d)

chrono 리터럴 장단점

장점단점
가독성 향상namespace 선언 필요
타입 안전C++14 이상 필요
간결한 코드헤더에서 using 주의
컴파일 타임 체크큰 값 오버플로우 주의

실전 팁

  1. namespace 사용

    • 함수 내부나 cpp 파일에서만 using namespace 사용
    • 헤더 파일에서는 명시적 호출 사용
  2. 타입 변환

    • 정밀도 손실 주의 (1500msseconds)
    • duration_cast로 명시적 변환
  3. 성능

    • 리터럴은 컴파일 타임에 처리됨
    • 런타임 오버헤드 없음

다음 단계

  • C++ Chrono 가이드
  • C++ Duration
  • C++ Calendar and Timezone

관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |