探寻C++类 —— 隐式类类型转换

新春之际不忘继续狂奔,近期在看有关C++类及面向对象方面的知识,从定义到构造函数的探究,其中遇到一个点是对于C++类中,隐式类类型的转换。查阅《C++ Primer 4th Edition》有如下一个注解。

A constructor that can be called with a single argument defines an implicit conversion from the parameter type to the class type. ——《C++ Primer 4th Edition》

可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。

单从定义读出隐式转换是针对单形参的类构造函数,为了深入了解,根据C++ Primer的例子修改了一下。

#include <iostream>
#include <string>
using namespace std;
 
class Sales_item {
    string isbn;
    int page;
public:
    // default argument for page is 200
    Sales_item(const string &book, const int p = 200) : isbn(book), page(p) { }
    Sales_item() { }
    // isbn is same as item or not
    bool same_isbn(const Sales_item &item) {
        return isbn == item.isbn;
    }
};
 
int main() {
    Sales_item item("9-999-99999-9");
    Sales_item other("1-111-11111-1");
    cout << item.same_isbn(other) << endl;
    // ok: compare item and other
    string null_book = "9-999-99999-9";
    cout << item.same_isbn(null_book) << endl;
    // ok: builds a Sales_item and isbn equal to null_book
    cout << item.same_isbn(string("9-999-99999-9")) << endl;
    // ok: "9-999-99999-9" is converted into string,
    //     builds a Sales_item and isbn equal to the string
    cout << item.same_isbn(Sales_item("9-999-99999-9", 600)) << endl;
    // ok: builds a Sales_item with 200 pages explicitly
    // cout << one.same_isbn("9-999-99999-9") << endl;
    // error: "9-999-99999-9" is a char string but could not convert into Sales_item automatically
    return 0;
}
运行结果
运行结果

我们使用一个string类型作为一个期待Sales_item对象的实参传给Sales_item类的same_isbn函数。不管是初始化一个新的string类型变量还是直接整合到语句中,结果都为1。

编译器使用接受一个string类型形参的Sales_item构造函数从null_book字符串生成一个新的Sales_item对象。新生成的临时Sales_item对象将被传递给same_isbn函数中。

按照C++ Primer中的注解,类构造函数首先需要为单个实参,在上述例子中如果将构造函数中的默认实参page去除,编译是无法通过的。

实际上,我们在构造对象Sales_item item时,系统已经进行了一次隐式转换,即从const char *的C字符串格式转换为string类型的过程,而正如例子中最后一种情况那样,将C字符串格式作为形参传入same_isbn函数时,系统并不能自动转换两次,需要自己手动用string初始化来强制转换后,系统才会利用Sales_item构造函数将string类型隐式转换为Sales_item对象。当然,这一步也可以自己手动完成,这就是下面要说的显式使用构造函数。

为了防止隐式转换,防止用户误操作,C++提供了一种抑制由构造函数定义的隐式转换的办法。即explicit关键字。

在构造函数前添加explicit关键字,这时隐式转换将被禁止,导致编译失败。explicit关键字只能用于类内部的构造函数声明上,在类定义体外部所做的定义不必重复此关键字。需要注意的是,在使用explicit关键字情况下,显式转换还是被允许的,且显式转换不受如前所述的形参个数的限制。使用显式转换可参见上面的例子中

item.same_isbn(Sames_item("9-999-99999-9", 600));

对于explicit关键字的使用,C++ Primer中建议单形参构造函数应设置为explicit(特殊情况除外),以避免错误。