2,More Effective C++——条款5(谨慎使用定制“类型转换函数”)

1 隐式类型转换

C++中允许如下3种形式的隐式类型转换:

1. 基本类型隐式类型转换:
int a = 10;
double b = a;

2. 单参数构造构造函数
class Name { // 可以将char* 类型转换成Name类型
	Name(const char* str) {}
};
class Apple { // 可以将int类型转换成Apple类型
	Apple(int a, double b = 10);
};

3,自定义隐式类型转换操作符:关键字operator后加上类型名称。该函数无返回类型。
class Banana {
	Banana(int a, int b) {}
	operator double() const; // 将Banana转换成double类型
}
Banana ba(1, 2);
double b = 2 + ba; // ba隐式转换成double类型

2 带来的问题

1 问题一

隐式类型转换会带来一些我们所不期望、或者想不到的副作用。比如下面情况。显然我们需要为Banana类重载一个“<<”操作符,但是由于Banana类可以隐式转换成double类型,即便不重载"<<"操作符,下面的输出语句也可以成功执行。

Banana ba(1, 2)
std::cout << ba;

因此,我们最后为Banana类,直接定义一个类型转换函数。每次需要Banana类的double类型时,直接调用该函数即可。相应地,STL中string类型也无法隐式转换成const char*类型。

double Banana::toDouble(){
}
2 问题二

下面代码展示了,单一构造函数导致类型转换的问题。由于List类定义了传入一个int类型的构造函数,因此b[index]作为一个int类型,可以转换成List类型,从而调用了List的重载函数:==。变成两个List对象进行比较。

template<class T> class List {
public:
	List(int size) {}
	T& operator[] (int index);
	bool operator ==(const List<int>& lh, const List<int>& rh) ;
}

List<int> a(10), b(10);
if (a == b[0]) { // b[index]进行类型转换成了一个List类对象
		// do somthing else
}

3 解决隐式类型转换带来的问题

有两种方式可以解决,上面提出的单一参数构造函数带来的隐式类型转换问题。

1 使用explicit关键字

C++引入了explicit修饰符,来修饰单一参数构造函数带来的隐式类型转换问题。explicit禁止隐式类型转换,但是允许显式类型转换。

template<class T> class List {
	explicit List(int size) {}
	......
}

List<int> a(10), b(10);
if (a == b[1]) ... // 非法,类型不匹配
if (a == List<int>(b[1])) ... // 合法,定义一个新List对象进行比较
if (a == static_cast<List<int> >(b[0]))...... // 合法,但是无意义
if (a == (List<int>)(b[0]))...... // 合法,但是无意义
2 使用内部类防止类型转换

C++不允许进行两次隐式类型转换,比如下面代码就是非法的:int转换成List,List又转换成Vector。(一次转换路径可以确认,对于两次转换路径,无法知道第二次转换应该走那一条路径)

class List {
public:
	List(int size);
}
class Vector {
public:
	Vector(const List& list);
}

List list(10);
Vector vector(list);
if (list == 10)..... // 合法,单一参数构造函数造成隐式类型转换
if (vector == 10) .... // 非法,连续两次类型转换

下面代码将List的长度"int size"封装到内部类型ListSize中。初始化时,int可隐式转换成ListSize类型,对List进行初始化。而a == b[1]进行比较时,由于int无法进行两次转换成List,因此编译错误。

template<class T> class List {
public:
	class ListSize {
		public:
			ListSize(int size) : m_size(size){}
			int size() {return m_size;}
		private:
			int m_size;
	}	
	List(ListSize size); // 此处将自定义类型作为参数传入,从而避免基本类型进行隐式转换
};

bool operatr ==(const List<int>& lh, const List<int>& rh);
List<int> a(10), b(10);  // 合法,int类型转换成ListSize, 用ListSize对List进行初始化
if (a == b[1]) .... // 非法,int类型只能转换成ListSize类型,不能转换成List类型
作者 : Scott Meyers 译序、导读 : 侯捷 译序(侯捷) C++ 是一个难学易用的语言! C++ 的难学,不仅在其广博的语法,以及语法背後的语意,以及语意背後的深层思维,以及深层思维背後的物件模型;C++ 的难学,还在於它提供了四种不同(但相辅相成)的程式设计思维模式:procedural-based,object-based,object-oriented,generic paradigm。 世上没有白吃的午餐。又要有效率,又要有弹性,又要前瞻望远,又要回溯相容,又要能治大国,又要能烹小鲜,学习起来当然就不可能太简单。 在如此庞大复杂的机制下,万千使用者前仆後续的动力是:一旦学成,妙用无穷。C++ 相关书籍之多,车载斗量;如天上繁星,如过江之鲫。广博如四库全书者有之(The C++ Programming Language、C++ Primer),深奥如重山复水者有之(The Annotated C++ Reference Manual, Inside the C++ Object Model),细说历史者有之(The Design and Evolution of C++, Ruminations on C++),独沽一味者有之(Polymorphism in C++, Genericity in C++),独树一帜者有之(Design Patterns,Large Scale C++ Software Design, C++ FAQs),程式库大全有之(The C++ Standard Library),另辟蹊径者有之(Generic Programming and the STL),工程经验之累积亦有之(Effective C++, More Effective C++, Exceptional C++)。 这其中,「工程经验之累积」对已具C++ 相当基础的程式员而言,有著致命的吸引力与立竿见影的帮助。Scott Meyers 的Effective C++ 和More Effective C++ 是此类佼佼,Herb Sutter 的Exceptional C++ 则是後起之秀。 这类书籍的一个共通特色是轻薄短小,并且高密度地纳入作者浸淫於C++/OOP 领域多年而广泛的经验。它们不但开展读者的视野,也为读者提供各种C++/OOP 常见问题或易犯错误的解决模型。某些小范围主题诸如「在base classes 中使用virtual destructor」、「令operator= 传回*this 的reference」,可能在百科型C++ 语言书籍中亦曾概略提过,但此类书籍以深度探索的方式,让我们了解问题背後的成因、最佳的解法、以及其他可能的牵扯。至於大范围主题,例如smart pointers, reference counting, proxy classes,double dispatching, 基本上已属design patterns 的层级! 这些都是经验的累积和心血的结晶。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值