C++ explicit Keyword | "explicit Keyword" Guide

C++ explicit Keyword | "explicit Keyword" Guide

이 글의 핵심

C++ explicit. Preventing implicit conversions, constructors, and conversion operators.

What is explicit?

The explicit keyword is used with constructors and conversion operators to prevent implicit conversions. It is particularly useful for avoiding unintended conversions during copy initialization with = expr. Most constructors for smart pointers are also explicit for this reason.

Comparison: With and Without explicit

CaseWithout 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) {}  // Allows 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 initialization

Conversion Process Diagram

graph LR
    A[int value] -->|Without explicit| B[Implicit Conversion]
    B --> C[Constructor Call]
    C --> D[Object Creation]
    
    A -->|With explicit| E{Initialization Method}
    E -->|Copy Initialization =| F[Compile Error]
    E -->|Direct Initialization| G[Constructor Call]
    G --> D

Using explicit with Constructors

Problem Scenario

graph TD
    A[Function Call: process 10] --> B{Constructor explicit?}
    B -->|No| C[Allows Implicit Conversion]
    C --> D[Temporary Array 10 Object Created]
    D --> E[Function Execution]
    E --> F[Unintended Behavior]
    
    B -->|Yes| G[Compile Error]
    G --> H[Explicit Conversion Required]
    H --> I[process Array 10]
    I --> J[Clear Intent]
class Array {
public:
    // ❌ Allows 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 Workflow:

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

Example 4: Copy Constructor

class Widget {
public:
    Widget() = default;
    
    // Explicit Copy Constructor (Rare)
    explicit Widget(const Widget& other) {
        // ...
    }
};

Widget w1;
// Widget w2 = w1;  // Error
Widget w2(w1);  // OK

C++11 explicit Extensions

explicit Support by C++ Version

FeatureC++98C++11C++20
Constructor
Conversion Operator
Conditional explicitexplicit(bool)
Multi-Argument Constructor✅ (Brace Initialization)
// C++11: explicit for Conversion Operators
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 -->|Without explicit| C[int → String Implicit Conversion]
    C --> D[Temporary String 10 Created]
    D --> E[Function Execution]
    E --> F[⚠️ Potential Bug]
    
    B -->|With explicit| G[❌ Compile Error]
    G --> H[Developer Expresses Intent Clearly]
    H --> I[process String 10]
    I --> J[✅ Safe Code]
// ❌ Without explicit
class String {
public:
    String(int size) {}
};

void process(String s) {}

process(10);  // Unintended conversion

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

Real-World Examples:

ScenarioWithout explicitWith explicit
process(10)Creates String(10) and passes itCompile Error
String s = 10Creates String(10)Compile Error
String s(10)Creates String(10)Creates String(10)
return 10Returns 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 Arguments

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 {  // Without explicit
        return ptr != nullptr;
    }
    
private:
    int* ptr;
};

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

// ✅ With 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[Explicit Cast Required]

Allowed bool Conversion Contexts:

Contextexplicit boolDescription
if (obj)✅ AllowedConditional statements
while (obj)✅ AllowedLoop conditions
obj && x✅ AllowedLogical operators
!obj✅ AllowedLogical NOT
bool b = obj❌ ErrorCopy initialization not allowed
int x = obj❌ ErrorInteger conversion not allowed

Recommendations for explicit Usage

explicit Decision Flow

graph TD
    A[Writing 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 Not Needed]
    
    G -->|Yes| E
    G -->|No| C

Code Guidelines

// ✅ Recommended explicit Usage
// 1. Single-Argument Constructor
explicit MyClass(int x);

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

// 3. bool Conversion Operator (Mandatory)
explicit operator bool() const;

// ❌ explicit Not Needed
// 1. Multi-Argument Constructor
MyClass(int x, int y);  // No implicit conversion

// 2. Copy/Move Constructor
MyClass(const MyClass&);  // Rarely explicit

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

Practical Checklist

ScenarioUse explicitReason
Vector(size_t size)✅ RequiredPrevent unintended size conversions
String(const char*)❌ OptionalString literal conversion is natural
operator bool()✅ RequiredPrevent unintended integer conversions
operator int()✅ RecommendedPrevent unintended arithmetic operations
Widget(int, int)❌ Not NeededMulti-argument constructors don’t allow implicit conversions
unique_ptr(T* ptr)✅ RequiredPrevent automatic pointer conversions

FAQ

Q1: When should I use explicit?

A:

  • Single-argument constructors
  • Conversion operators
  • To prevent unintended conversions

Q2: Does explicit affect performance?

A: No. It is a compile-time check.

Q3: What about copy constructors?

A: Rarely explicit. Only in special cases.

Q4: What changed in C++11?

A: explicit can now be used with conversion operators.

Q5: What about bool conversions?

A: explicit is recommended to prevent unintended conversions.

Q6: Where can I learn more about explicit?

A:

  • “Effective C++”
  • “C++ Primer”
  • cppreference.com

Related Posts: Type Conversion, Copy Initialization, Smart Pointers.

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

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

- [C++ 타입 변환 | "Type Conversion" 가이드](/blog/cpp-type-conversion/)
- [C++ Copy Initialization | "복사 초기화" 가이드](/blog/cpp-copy-initialization/)
- [C++ 복사/이동 생성자 | "Rule of Five" 가이드](/blog/cpp-copy-move-constructor/)
- [C++ 스마트 포인터 | unique_ptr/shared_ptr "메모리 안전" 가이드](/blog/cpp-smart-pointers/)

---

---

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

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