认识 C++ 中的 explicit 关键字

带单一参数的构造函数在缺省情况下隐含一个转换操作符,请看下面的代码:

class C {
int i;
//...
public:
      C(int i);//constructor and implicit conversion operator
//as well
};

void f() {

C c(0);

c = 5; //将 5 隐式转换为 C 对象,然后赋值

}

编译器重新编辑上述例子代码,如下:

//
//"c=5;" 被编译器转换成下面这个样子:
/

C temp(5);// 实例化一个临时对象,
c = temp; // 用 = 赋值
temp.C::~C(); // temp 的析构函数被激活

在很多情况下,这个转换是有意的,并且是正当的。但有时我们不希望进行这种自动的转换,例如:

class String {
int size;
char *p;
//..
public:
       String (int sz); //这里不希望进行隐式转换操作
};
void f ()
{
    String s(10);

    // 下面是一个程序员的编码;发生一个意想不到的转换:

    s = 100; // 糟糕,100 被转换为一个 String,然后被赋值给 s
}

为了避免这样的隐式转换,应该象下面这样显式声明该带单一参数的构造函数:

class String {
int size;
char *p;
//..
public:
       // 不要隐式转换
       explicit String (int sz);
       String (const char *s, int size n = 0); // 隐式转换
};

void f ()
{
    String s(10);

    s = 100; // 现在编译时出错;需要显式转换:

    s = String(100); // 好;显式转换
    s = "st";        // 好;此时允许隐式转换
}

这个 《ANSI/ISO C++ Professional Programmer's Handbook 》是这样说的

explicit Constructors
A constructor that takes a single argument is, by default, an implicit conversion operator, which converts its argument to
an object of its class (see also Chapter 3, "Operator Overloading"). Examine the following concrete example:
class string
{
private:
int size;
int capacity;
char *buff;
public:
string();
string(int size); // constructor and implicit conversion operator
string(const char *); // constructor and implicit conversion operator
~string();
};
Class string has three constructors: a default constructor, a constructor that takes int, and a constructor that
constructs a string from const char *. The second constructor is used to create an empty string object with an
initial preallocated buffer at the specified size. However, in the case of class string, the automatic conversion is
dubious. Converting an int into a string object doesn't make sense, although this is exactly what this constructor does.

Consider the following:
int main()
{
string s = "hello"; //OK, convert a C-string into a string object
int ns = 0;
s = 1; // 1 oops, programmer intended to write ns = 1,
}
In the expression s= 1;, the programmer simply mistyped the name of the variable ns, typing s instead. Normally,
the compiler detects the incompatible types and issues an error message. However, before ruling it out, the compiler first
searches for a user-defined conversion that allows this expression; indeed, it finds the constructor that takes int.
Consequently, the compiler interprets the expression s= 1; as if the programmer had written
s = string(1);
You might encounter a similar problem when calling a function that takes a string argument. The following example
can either be a cryptic coding style or simply a programmer's typographical error. However, due to the implicit
conversion constructor of class string, it will pass unnoticed:
int f(string s);
int main()
{
f(1); // without a an explicit constructor,
//this call is expanded into: f ( string(1) );
//was that intentional or merely a programmer's typo?
}
'In order to avoid such implicit conversions, a constructor that takes one argument needs to be declared explicit:
class string
{
//...
public:
explicit string(int size); // block implicit conversion
string(const char *); //implicit conversion
~string();
};
An explicit constructor does not behave as an implicit conversion operator, which enables the compiler to catch the
typographical error this time:
int main()
{
string s = "hello"; //OK, convert a C-string into a string object
int ns = 0;
s = 1; // compile time error ; this time the compiler catches the typo
}
Why aren't all constructors automatically declared explicit? Under some conditions, the automatic type conversion is
useful and well behaved. A good example of this is the third constructor of string:
string(const char *);

The implicit type conversion of const char * to a string object enables its users to write the following:
string s;
s = "Hello";
The compiler implicitly transforms this into
string s;
//pseudo C++ code:
s = string ("Hello"); //create a temporary and assign it to s
On the other hand, if you declare this constructor explicit, you have to use explicit type conversion:
class string
{
//...
public:
explicit string(const char *);
};
int main()
{
string s;
s = string("Hello"); //explicit conversion now required
return 0;
}
Extensive amounts of legacy C++ code rely on the implicit conversion of constructors. The C++ Standardization
committee was aware of that. In order to not make existing code break, the implicit conversion was retained. However, a
new keyword, explicit, was introduced to the languageto enable the programmer to block the implicit conversion
when it is undesirable. As a rule, a constructor that can be invoked with a single argument needs to be declared
explicit. When the implicit type conversion is intentional and well behaved, the constructor can be used as an
implicit conversion operator. 

### C++ 中 `explicit` 关键字的作用和用法 #### 单参数构造函数中的使用 在C++中,`explicit`关键字主要用于防止编译器执行不必要的隐式类型转换。当定义了一个只接受单一参数的构造函数时,如果不加`explicit`修饰,则该构造函数可以被用来实现从其参数类型的对象到类类型的隐式转换。这可能会导致一些难以察觉的错误。 通过声明为`explicit`,能够阻止这些潜在危险的行为发生,使得只有显式的强制转换才能触发此类构造过程[^1]: ```cpp class MyClass { public: // 不带 explicit 的构造函数允许隐式转换 MyClass(int value); }; void func(MyClass obj); int main() { int num = 5; // 下面这一行会因为存在无参构造而成功编译并运行, // 导致意料之外的对象创建行为。 func(num); } ``` 如果我们将上述例子中的构造函数改为带有`explicit`的关键字形式: ```cpp class MyClass { public: // 带有 explicit 的构造函数不允许隐式转换 explicit MyClass(int value); }; ``` 此时再尝试传递整数给期望接收`MyClass`实例的地方将会引发编译期报错,除非程序员明确指定了类型转换操作。 #### 转换运算符上的应用 除了应用于单个参数的构造函数外,`explicit`也可以标注于用户自定义类型之间的转换运算符上。同样地,这样做是为了避免不希望发生的自动转型情况的发生: ```cpp class IntWrapper { private: int m_value; public: // 显式指定此转换仅能由显示转换完成 explicit operator bool() const noexcept { return static_cast<bool>(m_value); } }; ``` 在这个案例里,即使有一个布尔上下文环境(比如条件判断),也不会让`IntWrapper`对象直接参与逻辑表达式的求值;相反,必须采用静态或动态方式来实施必要的转变动作。 关于多参数构造的情况,并不在`explicit`的应用范围内,因此对于这种情况下的处理方法并不涉及`explicit`关键字本身[^2]。然而值得注意的是,在某些版本之后的标准库支持下,可以通过大括号语法来进行初始化列表风格的新建工作,但这属于另一个话题了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值