目录 Table of Contents
3 non-explicit-one-argument ctor 具有一个实参的构造函数
non-explicit-one-argument ctor
class Fraction
{
public:
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den){ }
Fraction operator+(const Fraction& f){
return Fraction(/*...*/);
}
private:
int m_numerator;
int m_denominator;
};
这里的代码和上一节的代码有些不一样, 这里的代码没有那个转换函数
我们这里的 Fraction 构造函数相当特别, 我们特别把这一种构造函数叫作 non-explicit-one-argument ctor 具有一个实参的构造函数. 这个构造函数有两个参数, 但后面那个参数有默认值, 所以你创建对象的时候只用给一个参数就可以了.
这样的设计其实是合理的, 在数学上比如整数 3 其实就是 3/1, 分母为 1 当作默认值.
这里是 2 parameter 但是是 1 argument. 1 argument 的意思是只要一个实参就够了. 当然你给它两个实参也可以.
那么 non-explicit 是什么意思呢 ? explicit
是一个关键字, 可以出现在构造函数的前面, 这里没有加 explicit
, 所以叫 'non-explicit-one-argument ctor'.
它有什么特性, 我们来看下面的使用示例代码
Fraction f(3, 5);
Fraction d2 = f + 4; // 调用 non-explicit ctor 将 4 转为 Fraction(4, 1)
//然后调用 operator+
我创建了一个五分之三的对象, 然后让它加 4.
刚刚提到了, 编译器遇到你的一个语句, 它会想办法去找能不能让你这条语句通过. 它现在看到 +
, 于是它就去找, 找到了你的运算符重载. 但是它发现这个 +
的动作是分数和分数相加, 你这里右边是一个整数 4, 这个形式和我的设计不同, 于是编译器会去看看能不能把 4 转换为 Fraction. 然后编译器发现有这么一个构造函数, 于是它就用这个函数把 4 转为 4/1 了. 这个时候就是分数与分数相加, 就可以调用 operator+
函数了.
non-explicit-one-argument ctor 的作用是可以把别的东西, 转换为这种东西. 而上一节是把这种东西转换为别的东西, 方向正好相反.
conversion function vs. non-explicit-one-argument ctor
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den){ }
operator double() const {
return (double)(m_numerator / m_denominator);
}
Fraction operator+(const Fraction& f){
return Fraction(/*...*/);
}
private:
int m_numerator;
int m_denominator;
}
如果转换函数和具有一个实参的构造函数并存, 并且下面应用的代码写成这样的话
Fraction f(3, 5);
Fraction d2 = f + 4; // [Error] ambiguous 歧义, 二义
这个时候编译器就不知道该怎么办了
编译器是要把 4 转为 fraction 吗 ? 构造函数可以把 4 转换为 fraction, 然后也有 fraction 的运算符重载, 看起来这条路走得通. 但是这里的 operator double()
函数是把 fraction 转为 double, 4 + 0.6 = 4.6, 然后 4.6 转为 d2, d2 是一个 fraction. 然后编译器发现两条路都可行, 于是编译器就不知道该走哪一条了, 所里这里编译器就报错了
这里两个函数并存会导致 ambiguous, 然而在上一节的代码里, 两个函数也是并存的但是可以编译通过. 注意两者的区别.
explicit-one-argument ctor
explicit, 明白的明确的, 就是告诉编译器不要自动地帮我做事情
class Fraction
{
public:
explicit Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) { }
operator double() const {
return (double)(m_numerator / m_denominator);
}
Fraction operator+(const Fraction& f){
return Fraction(/*...*/);
}
private:
int m_numerator;
int m_denominator;
};
这里 Fraction 既然设计是构造函数, 那它就只是一个构造函数. 所以现在我把这里加上 explicit
, 这样一来的话, 就是告诉编译器说, 你不可以自动地把 3 变成 3/1 这样
我们来看使用代码
Fraction f(3, 5);
Fraction d2 = f + 4; // [Error] conversion from 'double' to 'Fraction' requested
现在是 3/5 加 4, 这个 4 就不会变成 4/1 了
于是这个加法就失败了, 因为这个加法的运算符重载函数里面写着左右两边都必须是分数 Fraction, 现在右边 4 转换不过去, 就报错了
这个编译器的报错信息是说, 编译器想从 double 转换成 Fraction, 意思是 4 要转为分数 4/1, 于是失败了
这个关键字用的地方其实很少, 只有在构造函数的前面用到. 其实在模板的一个很小的地方也用得到这个关键字, 这个地方太细节了也就很少人注意到它
所以基本上这个关键字是用在构造函数前面
conversion function, 转换函数
如果你很好奇标准库有没有哪个地方用到了转换函数, 这里就有一个例子
template<class Alloc>
class vector<bool, Alloc> // 一个容器, 模板的偏特化
{
public:
typedef __bit_reference reference;
protected:
// 对操作符中括号重载
/*
* 这个容器的意思是说它里面放的每个都是 boolean 值, 所以比如说要取出第一百个位置上面的真或假, 传出来的东西应该是一个 boolean 值.
* 但这里的设计是, 传出来的是 reference 这个东西, 这个是另外一个类 __bit_reference. 这个设计手法叫作代理, 用 reference 这个东西去代理代表它本来应该传回来的东西.
* 所以 reference 这个东西自然应该有一个转换函数转为 boolean.
* 你拿 A 去代表 B, 那 A 应该就有一个转换函数转为 B
* __bit_reference 的源代码在下面
*/
reference operator[](size_type n){
return *(begin() + difference_type(n));
}
//...
};
struct __bit_reference{
unsigned int* p;
unsigned int mask;
//...
public:
// 转换函数, 转为 boolean 值
operator bool() const { return !(!(*p & mask)); }
//...
}