第二章,操作符

条款5 对定制的“类型转换”函数保持警惕

描述

自定义隐式类型转换函数,易导致代码行为异常、编译歧义,非必要不使用,使用时需严格限制。

一、两类隐式类型转换的来源

1、转换运算符

格式:operator 目标类型() const;,将类对象隐式转为目标类型。

class Rational {
public:
int n, d;
operator double() const { return (double)n/d; } // Rational→double
};
Rational r(1,2);
double x = r * 2.0; // 自动转换为double运算

2、单参数构造函数

含默认参数的多参构造也归为此类,允许参数类型隐式转为类对象。

class Rational {
public:
Rational(int n, int d=1) : n(n), d(d) {} // int→Rational
};
Rational r1(1,2);
Rational r2 = r1 + 5; // 5自动转为Rational对象

二、隐式转换的核心问题

1、编译歧义:多转换路径时编译器无法判断,直接报错;
2、行为不可控:隐式转换改变运算逻辑,结果不符合预期;
3、可读性差:无显式标记,维护时难以发现转换行为。

// 同时定义上述两种转换,触发编译歧义
class Rational {
public:
Rational(int n, int d=1) : n(n), d(d) {}
operator double() const { return (double)n/d; }
int n, d;
};
Rational r(1,2);
bool b = (r == 0.5); // 歧义:r转double 或 0.5转int再转Rational

三、解决隐式转换问题的3种方法

1、替换转换运算符

用具名显式函数替代,强制手动调用。

class Rational {
public:
double toDouble() const { return (double)n/d; } // 替代转换运算符
};
double x = r.toDouble() * 2.0; // 必须显式调用

2、构造函数加explicit

禁止参数类型隐式转为类对象,仅允许显式构造。

class Rational {
public:
explicit Rational(int n, int d=1) : n(n), d(d) {}
};
// Rational r2 = r1 +5; 编译报错,禁止隐式转换
Rational r3 = Rational(5); // 显式构造,合法

3、重载类的运算符

直接重载核心运算,避免通过隐式转换调用内置运算符。

class Rational {
public:
int n, d;
friend Rational operator+(const Rational& a, const Rational& b) {
return Rational(a.n*b.d+b.n*a.d, a.d*b.d);
}
};
Rational r2 = r1 + Rational(5); // 调用重载运算符,无隐式转换

四、核心使用原则

1、单参数构造函数默认加explicit;
2、不定义转换运算符,优先用具名显式函数;
3、避免双向隐式转换,显式转换优于隐式转换。

总结

隐式类型转换的便捷性远低于其带来的风险,工程中以显式为原则,通过explicit、具名函数、运算符重载三种方式,彻底规避隐式转换的问题。