More Effective C++(条款5:对定制的“类型转换函数”保持警觉)

1、C++ 允许内置数据类型之间(例如char和int,int和double等)进行隐式转换,对于内置类型之间的隐式转换有详细的规则,但不管怎样,这些都是语言提供的,既相对安全,我们又无法更改(也就是说,这些我们不用管也管不着);

2、对于自定义的类类型,单自变量constructor(有单一参数或有多个参数且除第一个参数外都有默认值隐式类型转换操作符(operator之后加上一个类型名称,不能指定返回值类型允许编译器允许执行隐式类型转换。

class Name{
public:
    Name(const string& s);//单自变量构造函数,可把string转换为Name
    ...
};
class Rational{
public:
    Rational(int numerator=0,int denominator=1);//可把int转换为Rational
    ...
};
class Rational{
public:
    ...
    operator double() const;//隐式类型转换操作符,将Rational转换为double
};

该函数会在以下情况被自动调用:

Rational r(1,2);
double d=0.5*r;  //r的值是1/2,将r转换为double,然后执行乘法

3. 隐式类型转换操作符operator type()意味着"需转则转,能转则转",也就是说,由于隐式转换过于灵活,某些情况下会导致类的设计者并不想出现的行为。

例如设计者定义了一个有理数类Rational,同时定义了 operator double(),而没有定义operator<<,这种情况下如果对于语句"Rational a(1,2);  cout<<a;",编译器应该报错来提醒设计者,但实际上编译器调用Rational::operator double()将a隐式转换为double然后输出,这背离了设计者的初衷。

 因而最好不要定义隐式类型转换操作符,取而代之的方法是定义像"double toDouble()"的函数来执行类型转换的功能,虽然使用时有些许不便,但可因为"不再默默调用那些不打算调用的函数而获得弥补。C++标准库中的string类从没有string到char* 的隐式类型转换操作符而采用c_str函数可能就是这个原因。

class Rational{
public:
    ...
    double toDouble() const;//将Rational转换为double
};
Rational r(1,2);
cout<<r;//错。Rational没有operator<<
cout<<r.toDouble();//对。以double形式输出r

4. 对于单自变量构造函数,由于隐式的转换,可能会出现更加隐蔽的错误,在很多方面比隐式类型转换操作符更不好对付,

例如:对于一个Array<int>对象,以下代码可能是正确的:

bool operator==(const Array<int>& lhs,const Array<int>& rls);
Array<int> a;
Array<int> b;
for(int i=0;i<10;++i)
    if(a==b[i]){}//注意这里a的[]被落掉了,但是如果定义了只接受一个int值做参数的Array<int>的构造函数则该代码语法没错

上段代码中if(a==b[i]),a为Array<int>,b[i]为int,但只接受一个int做参数的Array<int>的constructor可将int转换为Array<int> object

如果要去除单变量参数构造函数的隐式类型转换特性,可以采用explict关键字声明:(将constructor声明为explicit,不能因隐式类型转换的需要而调用它们)

template <class T>
class Array{
public:
    ...
    explicit Array(int size);//
    ...
};
Array<int> a(10);//数组a含有10个int元素
Array<int> b(10);

if(a==b[i])...//错!b[i]为int,无法将int转换为Array<int>
if(a==static_cast<Array<int>>(b[i]))//没问题
if(a==Array<int>(b[i]))//没问题。将int转换为Array<int>,显式转换
if(a==(Array<int>)b[i])//没问题。c旧式转换

如果编译器不支持explict关键字,那么可以在自定义类与内置类型之间加一层只封装一个内置类型成员的类,并利用5规则实现同样的效果,如:

class Array{
public:
    class ArraySize{
    public:
        ArraySize(int numElements): theSize(numElements){}
    private:
        int theSize;
    };
    Array(ArraySize size);//接收一个ArraySize对象,而非一个int
    ......
};

现在,通过Array的单自变量构造函数定义一个对象时:

Array<int> a(10);//编译器要求调用 Array<int> class中的自变量为int的constructor,但不存在。

编译器知道:ArraySize(int numElements)可以将int转换为一个临时的ArraySize对象,而该对象正是Array(ArraySize size)需要的。转换,函数调用成功。

其中ArraySize被称为proxy(代理人) class(见条款30) ,这样唯一的缺点是在使用单一参数构造对象时中间加了一层转换,不过是值得的

5. 对于自定义类型的类型转换,有一个规则:”没有任何一个转换程序可以内含一个以上的‘用户定制转换行为’(亦即单自变量constructor亦即隐式类型转换操作符)“,也就是说,必要的时候编译器可以先进行内置类型之间的转换再调用带单自变量构造函数或者先调用隐式类型转换操作符再进行内置类型之间的转换,但不可能连续进行两次用户定制的类型转换!

针对上面内嵌ArraySize类的Array类:

bool operator==(const Array<int>& lhs,const Array<int>& rls);
Array<int> a;
Array<int> b;
for(int i=0;i<10;++i)
    if(a==b[i]){}//错
//编译器需要类型为Array<int>的对象在“==”右边,以针对Array<int>对象调用operator==
//但没有以int为参数的单一自变量constructor
//编译器不能考虑将int转换为临时性ArraySize对象,然后再根据该临时性对象产生Array<int>对象
//(调用两个用户定制转换行为(将int转换为ArraySize,将ArraySize转换为Array)是禁止的)
6. 总结:允许编译器执行隐式转换弊大于利,所以不要提供转换函数




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值