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
| Case | Without explicit | With explicit |
|---|---|---|
| Copy Initialization | String s = 10; ✅ | String s = 10; ❌ |
| Direct Initialization | String s(10); ✅ | String s(10); ✅ |
| Brace Initialization | String s{10}; ✅ | String s{10}; ✅ |
| Function Argument | func(10); ✅ | func(10); ❌ |
| Return Value | return 10; ✅ | return 10; ❌ |
| static_cast | static_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
| Feature | C++98 | C++11 | C++20 |
|---|---|---|---|
| Constructor | ✅ | ✅ | ✅ |
| Conversion Operator | ❌ | ✅ | ✅ |
| Conditional explicit | ❌ | ❌ | ✅ explicit(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:
| Scenario | Without explicit | With explicit |
|---|---|---|
process(10) | Creates String(10) and passes it | Compile Error |
String s = 10 | Creates String(10) | Compile Error |
String s(10) | Creates String(10) | Creates String(10) |
return 10 | Returns 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:
| Context | explicit bool | Description |
|---|---|---|
if (obj) | ✅ Allowed | Conditional statements |
while (obj) | ✅ Allowed | Loop conditions |
obj && x | ✅ Allowed | Logical operators |
!obj | ✅ Allowed | Logical NOT |
bool b = obj | ❌ Error | Copy initialization not allowed |
int x = obj | ❌ Error | Integer 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
| Scenario | Use explicit | Reason |
|---|---|---|
Vector(size_t size) | ✅ Required | Prevent unintended size conversions |
String(const char*) | ❌ Optional | String literal conversion is natural |
operator bool() | ✅ Required | Prevent unintended integer conversions |
operator int() | ✅ Recommended | Prevent unintended arithmetic operations |
Widget(int, int) | ❌ Not Needed | Multi-argument constructors don’t allow implicit conversions |
unique_ptr(T* ptr) | ✅ Required | Prevent 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 등으로 검색하시면 이 글이 도움이 됩니다.