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)

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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

Below is an implementation example using mermaid. Understand the role of each part while examining the code.

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]

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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:

Here is detailed implementation code using mermaid. Ensure stability through error handling. Understand the role of each part while examining the code.

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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality, perform branching with conditionals. Understand the role of each part while examining the code.

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)

Below is an implementation example using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

// 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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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

Below is an implementation example using mermaid. Understand the role of each part while examining the code.

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]

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the 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

Below is an implementation example using C++. Define a class to encapsulate data and functionality. Try running the code directly to check its operation.

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

Below is an implementation example using C++. Try running the code directly to check its operation.

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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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:

Below is an implementation example using mermaid. Understand the role of each part while examining the code.

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

Below is an implementation example using mermaid. Understand the role of each part while examining the code.

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

Here is detailed implementation code using C++. Understand the role of each part while examining the code.

// ✅ 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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality, perform branching with conditionals. Understand the role of each part while examining the code.

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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

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! 🚀