Why do we need to declare a constructor explicit?
There is a mechanism called implicit conversion in C and C++. When you call an expression that requires the type T1, however you give it a variable typed in T2, implicit conversion happens.
In C++, a user defined class might contains many constructors. For those who has only one parameter, a new implicit conversion from the type of the parameter to the user defined class is registered. For example, if you have the following definition of class Foo, the conversion from int to Foo is registered:
class Foo {
public:
Foo(int x) : x_(x) {}
private:
int x_ = 0;
// other data members
};
This time, everytime an object of type Foo (or related const lvalue reference or rvalue reference type) is required, passing a variable typed in int will trigger an implicit conversion, and construct a temporary object of type Foo.
#include <iostream>
class Foo {
public:
Foo(int x) : x_(x) {}
private:
int x_ = 0;
};
void bar(Foo&& foo) {
std::cout << "function[void bar(const Foo& foo)] called.\n";
}
int main() {
bar(42);
return 0;
}
In some cases, this kind of implicit conversion helps you; however, in other cases, it ruins your code and becomes a hard-to-debug defect. Whether it helps or not depends on the semantic of the expression that triggers the implicit conversion, which is impossible for the compile to detect.
#include <iostream>
#include <string>
struct Foo {
Foo() = default;
Foo(int x) : x_(x) {}
int compare(const Foo& other) { // designed to compare the length_ of two Foo objects
if (this->length_ < other.length_) { return -1; }
else if (other.length_ < this->length_) { return 1; }
else { return 0; }
}
int x_ = 0;
int length_ = 0;
};
int main() {
Foo bar;
bar.length_ = 100;
std::cout << bar.compare(50) << std::endl; // happens the implicit conversion
return 0;
}
In the above code, the one-parameter constructor of the class Foo takes an int variable and thus defines the conversion. Hence, when calling bar.compare(50), this conversion is triggered implicitly. However, in the constructor, the length_ of the temporary remains 0, which is not what we desired. Of course, we want to compare the length_ of two well constructed Foo objects. The origin of the problem roots in the implicitly triggered conversion. To solve the problem, the explicit keyword is used for prohibitting impilicit conversion (and explicit conversion is still allowed).