C++ explicit Keyword for Constructors Explained
This article explores the purpose and necessity of the
explicit keyword in C++, focusing on how it applies to
single-argument constructors. You will learn how C++ handles implicit
type conversions by default, the unintended bugs and ambiguities these
conversions can introduce, and how marking constructors as
explicit prevents the compiler from performing unwanted
automatic type casting, resulting in safer and more maintainable
code.
The Default Behavior: Implicit Conversion
In C++, any constructor that can be invoked with a single argument acts by default as an implicit conversion operator. This means the compiler is allowed to use that constructor silently to convert a value of the argument's type into an instance of the class.
Consider the following example:
class Buffer {
public:
Buffer(int size) {
// Allocates a buffer of 'size' bytes
}
};
void processBuffer(const Buffer& buf) {
// Process the buffer
}Because Buffer(int size) is not declared
explicit, the compiler permits operations such as:
Buffer b = 10; // Compiles: Implicitly converts int 10 to Buffer(10)
processBuffer(50); // Compiles: Implicitly converts int 50 to Buffer(50)While this can occasionally be convenient, it often produces
unintuitive and buggy behavior. Passing an integer like 50
to a function expecting a Buffer object hides logic errors,
as the reader or author of processBuffer(50) might assume
50 represents buffer data rather than an allocated
size.
The Role of the
explicit Keyword
The explicit keyword is a declaration specifier applied
to constructors to suppress implicit type conversions and
copy-initialization syntax.
When you prepend explicit to a constructor:
class Buffer {
public:
explicit Buffer(int size) {
// Allocates a buffer of 'size' bytes
}
};The compiler will reject implicit conversions:
Buffer b1 = 10; // Compiler Error: conversion from 'int' to non-scalar type 'Buffer' requested
processBuffer(50); // Compiler Error: could not convert '50' from 'int' to 'Buffer'Valid Instantiations with
explicit
Applying explicit does not prevent you from constructing
the object with that argument; it simply requires direct initialization
syntax or an explicit cast:
Buffer b1(10); // OK: Direct-initialization
Buffer b2{10}; // OK: Direct-list-initialization
Buffer b3 = Buffer(10); // OK: Explicit temporary created
processBuffer(Buffer(50)); // OK: Explicit conversion
processBuffer(static_cast<Buffer>(50));// OK: Explicit castWhen to Use explicit
As a general best practice in modern C++:
- Mark single-argument constructors as
explicitby default. This prevents surprising conversions where types do not conceptually match (e.g., converting an integer to a memory buffer, file handle, or collection size). - Omit
explicitonly when designing transparent wrapper types. If the constructor argument represents a natural, lossless equivalence—such as converting aconst char*to a customStringclass, or a numeric type to aComplexnumber class—omittingexplicitmay be justified to allow idiomatic assignment and function calls.
By enforcing explicit instantiation, you turn silent runtime misbehaviors into clear, immediate compile-time errors.