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
| Type | 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) ✅ |
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
| Feature | C++98 | C++11 | C++20 |
|---|---|---|---|
| Constructor | ✅ | ✅ | ✅ |
| Conversion operator | ❌ | ✅ | ✅ |
| Conditional explicit | ❌ | ❌ | ✅ explicit(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:
| Scenario | Without explicit | With explicit |
|---|---|---|
process(10) | Create String(10) and pass | Compile error |
String s = 10 | Create String(10) | Compile error |
String s(10) | Create String(10) | Create String(10) |
return 10 | Return 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:
| Context | explicit bool | Description |
|---|---|---|
if (obj) | ✅ Allowed | Conditional is explicit conversion |
while (obj) | ✅ Allowed | Loop condition |
obj && x | ✅ Allowed | Logical operator |
!obj | ✅ Allowed | Logical NOT |
bool b = obj | ❌ Error | Copy initialization forbidden |
int x = obj | ❌ Error | Integer 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
| Situation | Use explicit | Reason |
|---|---|---|
Vector(size_t size) | ✅ Required | Prevent unintended size conversion |
String(const char*) | ❌ Can omit | String literal conversion is natural |
operator bool() | ✅ Required | Prevent integer conversion |
operator int() | ✅ Recommended | Prevent unintended arithmetic operations |
Widget(int, int) | ❌ Unnecessary | Multi-argument has no implicit conversion |
unique_ptr(T* ptr) | ✅ Required | Prevent 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:
- “Effective C++” by Scott Meyers (Item 15)
- “C++ Primer” by Lippman, Lajoie, Moo
- cppreference.com - explicit specifier
Related articles: Type conversion, Copy initialization, Smart pointers.
One-line summary: explicit prevents implicit conversion in constructors and conversion operators to enhance type safety.
Related Articles
- C++ Type Conversion | “Type Conversion” Guide
- C++ Copy Initialization | “Copy Initialization” Guide
- C++ Copy/Move Constructor | “Rule of Five” Guide
- C++ Smart Pointers | unique_ptr/shared_ptr “Memory Safety” Guide
Master type safety with explicit! 🚀