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

1)C++允许内置数据类型之间进行隐式转换,比如char转int,int转double,对于内置数据类型的转换有详细的规则,但不管怎么样,这些都是语言提供的,相对安全,而且我们无法更改

对于自定义类的类型,其隐式类型可以通过带单一自变量的构造函数和隐式类型转换操作符来实现

2)单一自变量构造函数:指能够以单一自变量成功调用的构造函数,该构造函数可能只有一个参数,也可能有多个参数,并且除了第一个参数外其他的都有默认值

class Name
{
public:
    Name(const string &s)//可以把string转化为Name
    {
        
    }
};

3)隐式类型操作转换符

样例1:

#include<bits/stdc++.h>
using namespace std;

class Retional
{
private:
    int numerator;
    int denominator;
public:
    Retional(int x,int y)
    {
        numerator=x;
        denominator=y;
    }
    operator double() const//将 Retional 转换为 double
    {
        return numerator * 1.0 / denominator;
    }
};

int main()
{
    Retional r(1,2);

    cout<<r<<endl;//0.5

    double x=0.5*r;//隐式类型转换函数在这种情况下会被调用,很隐秘,将Retional类型转化为了double类型

    cout<<x<<endl;//0.25
}

隐式类型转换函数存在的问题其根本问题就在于,在你从未打算也未预期的情况下,此类函数可能会被调用,而且结果也可能是不正确的,不直观的程序行为,很难调试

解决方案提供一个功能对等的显式函数来取代隐式类型转换函数,通过显示的调用该函数来完成类型转换

#include<bits/stdc++.h>
using namespace std;

class Retional
{
private:
    int numerator;
    int denominator;
public:
    Retional(int x,int y)
    {
        numerator=x;
        denominator=y;
    }
    double toDouble() const //显式 类型转换函数
    {
        return numerator*1.0/denominator;
    }
};

int main()
{
    Retional r(1,2);
    
    //cout<<r<<endl; //error 没有重载<<
    
    cout<<r.toDouble()<<endl;//0.5 显式的调用转换函数比隐式的类型转换函数更加可靠

    double x=0.5*r.toDouble();

    cout<<x<<endl;//0.25
}

样例2:

#include<bits/stdc++.h>
using namespace std;

template<class T>
class Array
{
    Array(int lowbound,int highbound)
    {

    }
    Array(int size)
    {

    }
    T& operator [](int index)
    {

    }
};

bool operator==(const Array<int> &lhs,const Array<int> &rhs)
{

}

int main()
{
    Array<int> a(10);
    Array<int> b(10);
    
    for(int i=0;i<10;i++)
    {
        //应该是a[i]==b[i],但是此时编译器没有报错!!它会通过Array(int size)将b[i]隐式的转换成Array,
        //然后每次迭代都用a的内容和这个数组比较,
        //这不仅没有实现功能,并且很没有效率,因为必须产生和销毁这个临时变量
        if(a==b[i])
        {
            //do something
        }else
        {
            //do something
        }
    }
    return 0;
}

应该是a[i]==b[i],但是此时编译器没有报错!!它会通过Array(int size)将b[i]隐式的转换成Array,然后每次迭代都用a的内容和这个数组比较,这不仅没有实现功能,并且很没有效率,因为必须产生和销毁这个临时变量

解决方案1采用explicit关键字,禁止编译器对该关键字修饰的函数进行隐式类型转换

#include<bits/stdc++.h>
using namespace std;

template<class T>
class Array
{
public:

    Array(int lowbound,int highbound)
    {

    }
    explicit Array(int size)
    {

    }
    T& operator [](int index)
    {

    }
};

bool operator==(const Array<int> &lhs,const Array<int> &rhs)
{

}

int main()
{
    Array<int> a(10);
    Array<int> b(10);

    for(int i=0; i<10; i++)
    {
        //if(a==b[i]){}  //error 加了explicit无法隐式转换
        
        if(a==Array<int>(b[i]))//可行,调用显示构造函数
        {

        }
        if(a==static_cast<Array<int> >(b[i]))//可行,调用C++类型转换函数
        {

        }
        if(a==(Array<int>)(b[i]))//可行,C的旧式转型
        {

        }
    }
    return 0;
}

解决方案2:采用内部代理类

C++中存在这样一条规则:没有任何一个转换程序可以内含一个以上的“用户定制转换行为(即单自变量的构造函数和隐式类型转换符)”,也就是说,必要的时候编译器可以先进行内置类型之间的转换再调用带单自变量的构造函数或者先调用隐式类型转换符再进行内置类型的转换,但不可能连续进行两次用户定制的转换行为

#include<bits/stdc++.h>
using namespace std;

template<class T>
class Array
{
public:
    class ArraySize //内部代理类
    {
    private:
        int thesize;
    public:
        ArraySize(int numElements):thesize(numElements){}
        int size() const
        {
            return thesize;
        }
    };
    Array(int lowbound,int highbound)
    {

    }
    explicit Array(ArraySize size)//使用内部代理类进行参数声明
    {

    }
    T& operator [](int index)
    {

    }
};

bool operator==(const Array<int> &lhs,const Array<int> &rhs)
{

}

int main()
{
    Array<int> a(10);
    Array<int> b(10);

    for(int i=0; i<10; i++)
    {
        if(a==b[i])//因为内部代理类的存在,所以编译无法通过
        {

        }
    }
    return 0;
}

通过使用内部代理类,不但可以以一个整数作为构造函数的自变量来指定数组的大小,又能阻止一个整数被隐式的类型转换未一个临时的Array对象!

很值得学习的一种模式


避免隐式类型转换函数被调用的三种方式:

1)提供一个和隐式类型转换函数功能相同的显式函数

2)使用explicit修饰隐式类型转换函数,禁止该函数被调用

3)使用内部代理类


总结:允许编译器执行隐式类型转换,害处将多过好处,所以不要提供这种隐式的类型转换,除非你真的真的很需要!


posted @ 2019-10-29 15:28  西*风  阅读(239)  评论(0编辑  收藏  举报