본문으로 건너뛰기
Previous
Next
C++ explicit Keyword | 'explicit 키워드' 가이드

C++ explicit Keyword | 'explicit 키워드' 가이드

C++ explicit Keyword | 'explicit 키워드' 가이드

이 글의 핵심

explicit은 생성자·변환 연산자에 붙여 암시적 변환을 막는 키워드입니다. 복사 초기화 = expr에서 의도치 않은 변환이 일어나지 않게 할 때 쓰고, 스마트 포인터 생성자도 대부분 explicit입니다.

explicit이란?

explicit은 생성자·변환 연산자에 붙여 암시적 변환을 막는 키워드입니다. 복사 초기화 = expr에서 의도치 않은 변환이 일어나지 않게 할 때 쓰고, 스마트 포인터 생성자도 대부분 explicit입니다.

explicit 유무 비교

구분explicit 없음explicit 있음
복사 초기화String s = 10;String s = 10;
직접 초기화String s(10);String s(10);
중괄호 초기화String s{10};String s{10};
함수 인자func(10);func(10);
반환 값return 10;return 10;
static_caststatic_cast<String>(10)static_cast<String>(10)
class String {
public:
    String(int size) {}  // 암시적 변환 허용
};

String s = 10;  // OK: int -> String

// explicit 사용
class String {
public:
    explicit String(int size) {}
};

// String s = 10;  // 에러
String s(10);  // OK: 명시적 생성

변환 과정 다이어그램

다음은 mermaid 예제 코드입니다.

graph LR
    A[int 값] -->|explicit 없음| B[암시적 변환]
    B --> C[생성자 호출]
    C --> D[객체 생성]
    
    A -->|explicit 있음| E{초기화 방식}
    E -->|복사 초기화 =| F[컴파일 에러]
    E -->|직접 초기화 | G[생성자 호출]
    G --> D

생성자에 explicit

문제 상황

다음은 mermaid 예제 코드입니다.

graph TD
    A[함수 호출: process 10] --> B{생성자 explicit?}
    B -->|없음| C[암시적 변환 허용]
    C --> D[Array 10 임시 객체 생성]
    D --> E[함수 실행]
    E --> F[의도하지 않은 동작]
    
    B -->|있음| G[컴파일 에러]
    G --> H[명시적 변환 필요]
    H --> I[process Array 10]
    I --> J[의도 명확]
class Array {
public:
    // ❌ 암시적 변환 허용
    Array(int size) {}
};

void process(Array arr) {}

int main() {
    process(10);  // int -> Array (의도하지 않음)
}

// ✅ explicit 사용
class Array {
public:
    explicit Array(int size) {}
};

// process(10);  // 에러
process(Array(10));  // OK

실전 예시

예시 1: 기본 사용

class Vector {
    double* data;
    size_t size;
    
public:
    explicit Vector(size_t s) : size(s) {
        data = new double[size];
    }
    
    ~Vector() {
        delete[] data;
    }
};

void process(Vector v) {}

int main() {
    // process(10);  // 에러
    process(Vector(10));  // OK
}

예시 2: 변환 연산자

class Fraction {
    int numerator, denominator;
    
public:
    Fraction(int n, int d) : numerator(n), denominator(d) {}
    
    // ❌ 암시적 변환
    operator double() const {
        return (double)numerator / denominator;
    }
};

Fraction f(1, 2);
double d = f;  // 암시적 변환

// ✅ explicit 변환 연산자
class Fraction {
public:
    explicit operator double() const {
        return (double)numerator / denominator;
    }
};

// double d = f;  // 에러
double d = static_cast<double>(f);  // OK

변환 연산자 동작 흐름:

sequenceDiagram
    participant Code
    participant Compiler
    participant Op as Operator
    
    Code->>Compiler: double d = f;
    
    alt no explicit
        Compiler->>Op: implicit call
        Op->>Code: return double
        Note over Code: OK
    else with explicit
        Compiler->>Compiler: block implicit
        Compiler->>Code: compile error
    end
    
    Code->>Compiler: static_cast
    Compiler->>Op: explicit call
    Op->>Code: return double
    Note over Code: always OK

예시 3: bool 변환

class SmartPointer {
    int* ptr;
    
public:
    explicit SmartPointer(int* p) : ptr(p) {}
    
    // ✅ explicit bool
    explicit operator bool() const {
        return ptr != nullptr;
    }
};

int main() {
    SmartPointer sp(new int(10));
    
    if (sp) {  // OK: 조건문에서 허용
        std::cout << "유효" << std::endl;
    }
    
    // bool b = sp;  // 에러
    bool b = static_cast<bool>(sp);  // OK
}

예시 4: 복사 생성자

class Widget {
public:
    Widget() = default;
    
    // explicit 복사 생성자 (드물음)
    explicit Widget(const Widget& other) {
        // ...
    }
};

Widget w1;
// Widget w2 = w1;  // 에러
Widget w2(w1);  // OK

C++11 explicit 확장

C++ 버전별 explicit 지원

기능C++98C++11C++20
생성자
변환 연산자
조건부 explicitexplicit(bool)
다중 인자 생성자✅ (중괄호 초기화)
// C++11: 변환 연산자에도 explicit
class MyClass {
public:
    explicit operator int() const {
        return 42;
    }
};

MyClass obj;
// int x = obj;  // 에러
int x = static_cast<int>(obj);  // OK

C++20 조건부 explicit

template<typename T>
class Optional {
public:
    // 조건부 explicit
    template<typename U>
    explicit(!std::is_convertible_v<U, T>)
    Optional(U&& value) : data(std::forward<U>(value)) {}
    
private:
    T data;
};

// int -> long은 변환 가능 → explicit 아님
Optional<long> opt1 = 10;  // OK

// string -> int는 변환 불가 → explicit
// Optional<int> opt2 = std::string("10");  // 에러

자주 발생하는 문제

문제 1: 의도하지 않은 변환

다음은 mermaid 예제 코드입니다.

graph LR
    A[process 10 호출] --> B{String int 생성자}
    B -->|explicit 없음| C[int → String 암시적 변환]
    C --> D[임시 String 10 생성]
    D --> E[함수 실행]
    E --> F[⚠️ 버그 발생 가능]
    
    B -->|explicit 있음| G[❌ 컴파일 에러]
    G --> H[개발자가 의도 명확히 표현]
    H --> I[process String 10]
    I --> J[✅ 안전한 코드]
// ❌ explicit 없음
class String {
public:
    String(int size) {}
};

void process(String s) {}

process(10);  // 의도하지 않은 변환

// ✅ explicit
class String {
public:
    explicit String(int size) {}
};

실무 사례:

시나리오explicit 없을 때explicit 있을 때
process(10)String(10) 생성 후 전달컴파일 에러
String s = 10String(10) 생성컴파일 에러
String s(10)String(10) 생성String(10) 생성
return 10String(10) 반환컴파일 에러

문제 2: 복사 초기화

class Widget {
public:
    explicit Widget(int x) {}
};

// Widget w = 10;  // 에러
Widget w(10);  // OK
Widget w{10};  // OK (C++11)

문제 3: 함수 인자

func 함수의 구현 예제입니다.

void func(std::vector<int> vec) {}

// ❌ explicit 없으면
// func(10);  // int -> vector (의도하지 않음)

// std::vector 생성자는 explicit
func(std::vector<int>(10));  // OK

문제 4: bool 변환

class Pointer {
public:
    operator bool() const {  // explicit 없음
        return ptr != nullptr;
    }
    
private:
    int* ptr;
};

Pointer p;
int x = p;  // bool로 변환 후 int로 (의도하지 않음)

// ✅ explicit
explicit operator bool() const {}

bool 변환 문제 다이어그램:

다음은 mermaid 예제 코드입니다.

graph TD
    A[Pointer p] --> B{operator bool explicit?}
    
    B -->|없음| C[int x = p]
    C --> D[Pointer → bool]
    D --> E[bool → int]
    E --> F[⚠️ x = 0 or 1]
    F --> G[의도하지 않은 정수 변환]
    
    B -->|있음| H[int x = p]
    H --> I[❌ 컴파일 에러]
    I --> J[if p 는 OK]
    I --> K[명시적 캐스트 필요]

허용되는 bool 변환 컨텍스트:

컨텍스트explicit bool설명
if (obj)✅ 허용조건문은 명시적 변환
while (obj)✅ 허용반복문 조건
obj && x✅ 허용논리 연산자
!obj✅ 허용논리 NOT
bool b = obj❌ 에러복사 초기화 금지
int x = obj❌ 에러정수 변환 금지

사용 권장사항

explicit 사용 결정 플로우

다음은 mermaid 예제 코드입니다.

graph TD
    A[생성자/변환 연산자 작성] --> B{단일 인자 생성자?}
    B -->|예| C{의도된 암시적 변환?}
    B -->|아니오| D{변환 연산자?}
    
    C -->|아니오| E[explicit 사용 ✅]
    C -->|예| F[explicit 생략 가능]
    
    D -->|예| G{bool 변환?}
    D -->|아니오| H[explicit 불필요]
    
    G -->|예| E
    G -->|아니오| C

코드 가이드라인

C/C++ 예제 코드입니다.

// ✅ explicit 사용 권장
// 1. 단일 인자 생성자
explicit MyClass(int x);

// 2. 변환 연산자
explicit operator int() const;

// 3. bool 변환 연산자 (필수)
explicit operator bool() const;

// ❌ explicit 불필요
// 1. 다중 인자 생성자
MyClass(int x, int y);  // 암시적 변환 안됨

// 2. 복사/이동 생성자
MyClass(const MyClass&);  // explicit 드물음

// 3. 기본 생성자
MyClass();  // 인자 없음

실무 체크리스트

상황explicit 사용이유
Vector(size_t size)✅ 필수의도하지 않은 크기 변환 방지
String(const char*)❌ 생략 가능문자열 리터럴 변환은 자연스러움
operator bool()✅ 필수정수 변환 방지
operator int()✅ 권장의도하지 않은 산술 연산 방지
Widget(int, int)❌ 불필요다중 인자는 암시적 변환 안됨
unique_ptr(T* ptr)✅ 필수포인터 자동 변환 방지

실무 패턴

패턴 1: 스마트 포인터

template<typename T>
class UniquePtr {
    T* ptr_;
    
public:
    explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {}
    
    ~UniquePtr() { delete ptr_; }
    
    explicit operator bool() const { return ptr_ != nullptr; }
    
    T& operator*() const { return *ptr_; }
    T* operator->() const { return ptr_; }
};

// 사용
UniquePtr<int> ptr(new int(42));
if (ptr) {  // OK: 조건문
    std::cout << *ptr << '\n';
}
// bool b = ptr;  // 에러: 복사 초기화

패턴 2: 타입 안전 래퍼

class UserId {
    int id_;
    
public:
    explicit UserId(int id) : id_(id) {}
    
    int value() const { return id_; }
};

class OrderId {
    int id_;
    
public:
    explicit OrderId(int id) : id_(id) {}
    
    int value() const { return id_; }
};

void processUser(UserId uid) {}
void processOrder(OrderId oid) {}

int main() {
    UserId uid(123);
    OrderId oid(456);
    
    processUser(uid);  // OK
    // processUser(oid);  // 에러: 타입 불일치
    // processUser(123);  // 에러: explicit
}

패턴 3: 단위 타입

class Meters {
    double value_;
    
public:
    explicit Meters(double v) : value_(v) {}
    
    double value() const { return value_; }
};

class Kilometers {
    double value_;
    
public:
    explicit Kilometers(double v) : value_(v) {}
    
    explicit operator Meters() const {
        return Meters(value_ * 1000);
    }
};

void setDistance(Meters m) {}

int main() {
    Kilometers km(5.0);
    
    // setDistance(km);  // 에러: 암시적 변환 불가
    setDistance(static_cast<Meters>(km));  // OK: 명시적
    // setDistance(5.0);  // 에러: double → Meters 불가
}

FAQ

Q1: explicit은 언제 사용하나요?

A:

  • 단일 인자 생성자 (의도하지 않은 타입 변환 방지)
  • 변환 연산자 (특히 bool)
  • 타입 안전성이 중요한 래퍼 클래스

Q2: 성능 영향은?

A: 없습니다. explicit은 컴파일 타임 검사이므로 런타임 성능에 영향을 주지 않습니다.

Q3: 복사 생성자에도 explicit을 사용하나요?

A: 드뭅니다. 복사 생성자에 explicit을 사용하면 복사 초기화가 불가능해져 불편합니다. 특별한 이유가 있을 때만 사용합니다.

Q4: C++11에서 어떤 변화가 있었나요?

A: 변환 연산자에도 explicit을 사용할 수 있게 되었습니다.

explicit operator int() const;  // C++11

Q5: bool 변환 연산자는 항상 explicit인가요?

A: 권장됩니다. explicit operator bool()은 조건문에서는 사용 가능하지만, 정수로의 암묵적 변환을 막습니다.

Q6: C++20 조건부 explicit은 무엇인가요?

A: explicit(bool)로 컴파일 타임 조건에 따라 explicit 여부를 결정할 수 있습니다.

template<typename T, typename U>
explicit(!std::is_convertible_v<U, T>)
MyClass(U&& value);

Q7: 다중 인자 생성자는 explicit이 필요한가요?

A: 일반적으로 불필요합니다. 다중 인자 생성자는 암시적 변환이 발생하지 않습니다. 하지만 C++11 중괄호 초기화에서는 가능하므로 필요 시 사용합니다.

Q8: explicit 학습 리소스는?

A:

관련 글: 타입 변환, 복사 초기화, 스마트 포인터.

한 줄 요약: explicit은 생성자와 변환 연산자의 암시적 변환을 방지하여 타입 안전성을 높입니다.

관련 글: 타입 변환, 복사 초기화, 스마트 포인터.


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

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

관련 글

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「C++ explicit Keyword | ‘explicit 키워드’ 가이드」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「C++ explicit Keyword | ‘explicit 키워드’ 가이드」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


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

C++, explicit, conversion, function Object() { [native code] }, C++11 등으로 검색하시면 이 글이 도움이 됩니다.