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) ✅ |
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
| Feature | C++98 | C++11 | C++20 |
|---|---|---|---|
| Constructor | ✅ | ✅ | ✅ |
| Conversion operator | ❌ | ✅ | ✅ |
| Conditional explicit | ❌ | ❌ | ✅ explicit(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:
| 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
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:
| 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
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
| 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
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:
- “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! 🚀
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 타입 변환 완벽 가이드 | 4가지 캐스트 연산자 비교 (static_cast/dynamic_cast)
- C++ Copy Initialization | ‘복사 초기화’ 가이드
- C++ explicit Keyword | ‘explicit Keyword’ Guide
이 글에서 다루는 키워드 (관련 검색어)
C++, explicit, conversion, constructor, C++11 등으로 검색하시면 이 글이 도움이 됩니다.