class Sales_data{
public:
// 构造函数的三种形式
Sales_data() = default;
Sales_data(const string &book) : bookNo(book) { }
Sales_data(const string &book, const unsigned num, const double sellp, const double salep);
Sales_data(std::istream &is) { is >> *this; }
Sales_data &combine(const Sales_data &);
...
};
如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,有时我们把这种构造函数称作转换构造函数(converting constructor)。
在 Sales_data 类中,接受 string 的构造函数和接受istream 的构造函数分别定义了从这两种类型向 Sales data 隐式转换的规则。
也就是说,在需要使用 Sales data 的地方,我们可以使用 string 或者istream 作为替代:
string null_book = "9-999-99999-9";
// 构造一个临时的Sales_data对象
// 该对象的units_sold和revenue等于0,bookNo等于null_book
item.combine(null_book);
在这里我们用一个 string实参调用了 Sales data的 combine 成员。
该调用是合法的,编译器用给定的string自动创建了一个 Sales_data对象。新生成的这个(临时) Sales_data 对象被传递给 combine。因为 combine 的参数是一个常量引用,所以我们可以给该参数传递一个临时量。
只允许一步类型转换
编译器只会自动地执行一步类型转换。例如,因为下面的代码隐式地使用了两种转换规则,所以它是错误的:
// 错误:需要用户定义的两种转换
// (1)把“9-999-99999-9”转换成string
// (2)再把这个临时string转换成Sales_data
item.combine("9-999-99999-9");
如果我们想完成上述调用,可以显式地把字符串转换成 string 或者 Sales_data对象∶
// 正确:显示地转换成string,隐式地转换成Sales_data
item.combine(string("9-999-99999-9"));
// 正确:隐式地转换成string,显示地转换成Sales_data
item.combine(Sales_data("9-999-99999-9"));
类类型转换并不总是有效
是否需要从 string 到 Sales_data 的转换依赖于我们对用户使用该转换的看法。
在此例中,这种转换可能是对的。null_book 中的 string 可能表示了一个不存在的 ISBN 编号。
另一个是从 istream 到 Sales_data 的转换∶
// 使用istream构造函数创建一个函数传递给combine
item.combine(cin);
这段代码隐式地把 cin 转换成 sales_data,这个转换执行了接受一个 istream 的 Sales_data构造函数。
该构造函数通过读取标准输入创建了一个(临时的)Sales_data对象,随后将得到的对象传递给 combine。
Sales_data对象是个临时量,一旦combine完成我们就不能再访问它了。
实际上,我们构建了一个对象,先将它的值加到item中,随后将其丢弃。
抑制构造函数的隐式转换
在要求隐式转换的程序上下文中,我们可以通过将构造函数声明为 explicit 加以阻止:
class Sales_data{
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned &s, double p) : bookNo(s), units_sold(n), revenue(p * n) { }
explicit Sales_data(const std::string &s) : bookNo(s) { }
explicit Sales_data(std::istream &);
...
};
此时,没有任何构造函数能用于隐式地创建 Sales data 对象,之前的两种用法都无法通过编译。
关键字 explicit 只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为 explicit 的。只能在类内声明构造函数时使用 explicit 关键字,在类外部定义时不应重复:
explicit Sales_data::Sales_data(istream &is){
read(is, *this);
}
explicit函数只能用于直接初始化
发生隐式转换的一种情况是当我们执行拷贝形式的初始化时(使用=)。此时,我们只能使用直接初始化而不能使用explicit 构造函数∶
Sales_data item1(null_book); // 正确:直接初始化
// 错误:不能将explicit构造函数用于拷贝形式的初始化过程
Sales_data item2 = null_book;
为转换显式地使用构造函数
尽管编译器不会将 explicit 的构造函数用于隐式转换过程,但是我们可以使用这样的构造函数显式地强制进行转换:
// 正确:实参是一个显式构造的Sales_data对象
item.combine(Sales_data(null_book));
// 正确:static_cast可以使用explicit的构造函数
item.combine(static_cast<Sales_data>(cin));
在第一个调用中,我们直接使用 Sales_data的构造函数,该调用通过接受 string的构造函数创建了一个临时的 Sales_data 对象。在第二个调用中,我们使用 static_cast执行了显式的而非隐式的转换。其中, static_cast 使用istream 构造函数创建了一个临时的 Sales data 对象。