본문으로 건너뛰기 C++ explicit Keyword | 'explicit Keyword' Guide

C++ explicit Keyword | 'explicit Keyword' Guide

C++ explicit Keyword | 'explicit Keyword' Guide

이 글의 핵심

explicit is a keyword attached to constructors and conversion operators to prevent implicit conversion. Used to prevent unintended conversion in copy initialization = expr, and most smart pointer constructors are explicit.

What is explicit?

explicit is a keyword attached to constructors and conversion operators to prevent implicit conversion. Used to prevent unintended conversion in copy initialization = expr, and smart pointer constructors are mostly explicit.

Comparison with/without explicit

TypeWithout explicitWith explicit
Copy initializationString s = 10;String s = 10;
Direct initializationString s(10);String s(10);
Brace initializationString s{10};String s{10};
Function argumentfunc(10);func(10);
Return valuereturn 10;return 10;
static_caststatic_cast<String>(10)static_cast<String>(10)
class String {
public:
    String(int size) {}  // Allow implicit conversion
};

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

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

// String s = 10;  // Error
String s(10);  // OK: Explicit construction

explicit on Constructor

Problem Situation

graph TD
    A[Function call: process 10] --> B{Constructor explicit?}
    B -->|No| C[Allow implicit conversion]
    C --> D[Create Array 10 temporary object]
    D --> E[Execute function]
    E --> F[Unintended behavior]
    
    B -->|Yes| G[Compile error]
    G --> H[Need explicit conversion]
    H --> I[process Array 10]
    I --> J[Clear intent]
class Array {
public:
    // ❌ Allow implicit conversion
    Array(int size) {}
};

void process(Array arr) {}

int main() {
    process(10);  // int -> Array (unintended)
}

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

// process(10);  // Error
process(Array(10));  // OK

Practical Examples

Example 1: Basic Usage

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);  // Error
    process(Vector(10));  // OK
}

Example 2: Conversion Operator

class Fraction {
    int numerator, denominator;
    
public:
    Fraction(int n, int d) : numerator(n), denominator(d) {}
    
    // ❌ Implicit conversion
    operator double() const {
        return (double)numerator / denominator;
    }
};

Fraction f(1, 2);
double d = f;  // Implicit conversion

// ✅ explicit conversion operator
class Fraction {
public:
    explicit operator double() const {
        return (double)numerator / denominator;
    }
};

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

Conversion Operator Flow:

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

Example 3: bool Conversion

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: Allowed in conditional
        std::cout << "Valid" << std::endl;
    }
    
    // bool b = sp;  // Error
    bool b = static_cast<bool>(sp);  // OK
}

C++11 explicit Extension

explicit Support by C++ Version

FeatureC++98C++11C++20
Constructor
Conversion operator
Conditional explicitexplicit(bool)
Multi-argument constructor✅ (brace initialization)
// C++11: explicit on conversion operator too
class MyClass {
public:
    explicit operator int() const {
        return 42;
    }
};

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

C++20 Conditional explicit

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

// int -> long is convertible → not explicit
Optional<long> opt1 = 10;  // OK

// string -> int is not convertible → explicit
// Optional<int> opt2 = std::string("10");  // Error

Common Issues

Issue 1: Unintended Conversion

graph LR
    A[process 10 call] --> B{String int constructor}
    B -->|No explicit| C[int → String implicit conversion]
    C --> D[Create temporary String 10]
    D --> E[Execute function]
    E --> F[⚠️ Possible bug]
    
    B -->|With explicit| G[❌ Compile error]
    G --> H[Developer expresses intent clearly]
    H --> I[process String 10]
    I --> J[✅ Safe code]
// ❌ No explicit
class String {
public:
    String(int size) {}
};

void process(String s) {}

process(10);  // Unintended conversion

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

Real-world Cases:

ScenarioWithout explicitWith explicit
process(10)Create String(10) and passCompile error
String s = 10Create String(10)Compile error
String s(10)Create String(10)Create String(10)
return 10Return String(10)Compile error

Issue 2: Copy Initialization

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

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

Issue 3: Function Argument

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

// ❌ Without explicit
// func(10);  // int -> vector (unintended)

// std::vector constructor is explicit
func(std::vector<int>(10));  // OK

Issue 4: bool Conversion

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

Pointer p;
int x = p;  // Convert to bool then to int (unintended)

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

bool Conversion Issue Diagram:

graph TD
    A[Pointer p] --> B{operator bool explicit?}
    
    B -->|No| C[int x = p]
    C --> D[Pointer → bool]
    D --> E[bool → int]
    E --> F[⚠️ x = 0 or 1]
    F --> G[Unintended integer conversion]
    
    B -->|Yes| H[int x = p]
    H --> I[❌ Compile error]
    I --> J[if p is OK]
    I --> K[Need explicit cast]

Allowed bool Conversion Contexts:

Contextexplicit boolDescription
if (obj)✅ AllowedConditional is explicit conversion
while (obj)✅ AllowedLoop condition
obj && x✅ AllowedLogical operator
!obj✅ AllowedLogical NOT
bool b = obj❌ ErrorCopy initialization forbidden
int x = obj❌ ErrorInteger conversion forbidden

Usage Recommendations

explicit Usage Decision Flow

graph TD
    A[Write constructor/conversion operator] --> B{Single argument constructor?}
    B -->|Yes| C{Intended implicit conversion?}
    B -->|No| D{Conversion operator?}
    
    C -->|No| E[Use explicit ✅]
    C -->|Yes| F[Can omit explicit]
    
    D -->|Yes| G{bool conversion?}
    D -->|No| H[explicit unnecessary]
    
    G -->|Yes| E
    G -->|No| C

Code Guidelines

// ✅ Recommend using explicit
// 1. Single argument constructor
explicit MyClass(int x);

// 2. Conversion operator
explicit operator int() const;

// 3. bool conversion operator (required)
explicit operator bool() const;

// ❌ explicit unnecessary
// 1. Multi-argument constructor
MyClass(int x, int y);  // No implicit conversion

// 2. Copy/move constructor
MyClass(const MyClass&);  // explicit rare

// 3. Default constructor
MyClass();  // No arguments

Real-world Checklist

SituationUse explicitReason
Vector(size_t size)✅ RequiredPrevent unintended size conversion
String(const char*)❌ Can omitString literal conversion is natural
operator bool()✅ RequiredPrevent integer conversion
operator int()✅ RecommendedPrevent unintended arithmetic operations
Widget(int, int)❌ UnnecessaryMulti-argument has no implicit conversion
unique_ptr(T* ptr)✅ RequiredPrevent automatic pointer conversion

Practical Patterns

Pattern 1: Smart Pointer

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_; }
};

// Usage
UniquePtr<int> ptr(new int(42));
if (ptr) {  // OK: Conditional
    std::cout << *ptr << '\n';
}
// bool b = ptr;  // Error: Copy initialization

Pattern 2: Type-safe Wrapper

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);  // Error: Type mismatch
    // processUser(123);  // Error: explicit
}

Pattern 3: Unit Type

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);  // Error: Implicit conversion not allowed
    setDistance(static_cast<Meters>(km));  // OK: Explicit
    // setDistance(5.0);  // Error: double → Meters not allowed
}

FAQ

Q1: When to use explicit?

A:

  • Single argument constructor (prevent unintended type conversion)
  • Conversion operator (especially bool)
  • Wrapper classes where type safety is important

Q2: Performance impact?

A: None. explicit is compile-time check, so no runtime performance impact.

Q3: Use explicit on copy constructor?

A: Rare. Using explicit on copy constructor makes copy initialization impossible, which is inconvenient. Use only when there’s special reason.

Q4: What changed in C++11?

A: Can now use explicit on conversion operators too.

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

Q5: Should bool conversion operator always be explicit?

A: Recommended. explicit operator bool() can be used in conditionals but prevents implicit conversion to integer.

Q6: What is C++20 conditional explicit?

A: Can determine explicit based on compile-time condition with explicit(bool).

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

Q7: Do multi-argument constructors need explicit?

A: Generally unnecessary. Multi-argument constructors don’t have implicit conversion. But possible with C++11 brace initialization, so use when needed.

Q8: Learning resources for explicit?

A:

Related articles: Type conversion, Copy initialization, Smart pointers.

One-line summary: explicit prevents implicit conversion in constructors and conversion operators to enhance type safety.


Master type safety with explicit! 🚀


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

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


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

C++, explicit, conversion, constructor, C++11 등으로 검색하시면 이 글이 도움이 됩니다.