《More Effective C++》《操作符——5、对定制的“类型转换函数”保持警觉》

文章目录

  • 1、内置数据类型
    • (1)C++允许内置数据类型之间进行隐式转换
  • 2、自定义的类型
    • (2)单一自变量构造函数:
    • (3)隐式类型操作符转换
  • 2、总结:
  • 3、参考

C++继承了C的类型转换的特性,C++允许编译器在不同类型之间执行隐式转换。

1、内置数据类型

(1)C++允许内置数据类型之间进行隐式转换

比如char转化为int,int转换为double,有从高向低转换(数据缺失),也有从低向高转换;不管怎样,这些都是C++允许的,相对安全的。

2、自定义的类型

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

(2)单一自变量构造函数:

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

class Name
{
public:
	Name(const string& s)//可以把string 转换为name
	{
	...
	}
}

(3)隐式类型操作符转换

举个栗子:

#include
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
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<
    
    cout<<r.toDouble()<<endl;//0.5 显式的调用转换函数比隐式的类型转换函数更加可靠

    double x=0.5*r.toDouble();

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

再举一个更加典型的栗子:

template<class T>
class Array{
public:
	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++)
	{
		if(a==b[i]){
		//do something
		}
		else{
		//do something
		}
	}
	return 0;
}

应该是a[i]==b[I],但是此时编译器并没有报错!它会通过Array(int size)将b[i]隐式地转换为Array,每次迭代都会用a的内容和这个数组进行比较,所以他没有实现比较数组中每个元素的功能,而且效率低下。
面对这种问题有2种解决办法,你希望提供一个单自变量的constructors给客户使用,与此同时你也希望阻止编译器不分青红皂白地调用这样的constructors。
方法一:采用expilcit关键字,禁止编译器对该关键字修饰的函数进行隐式类型转换
举个栗子:

#include
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;
}

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

前置知识:嵌套类

  • 什么是嵌套类?
    嵌套类是定义在另一个类内部的类。这个被嵌套的类称为嵌套类,内部类或者成员类。嵌套类可以具有public,private,public的访问修饰符,决定了它在外部类以及外部类的派生类中的可见性。
    嵌套类的特性:
    (1)作用域: 嵌套类的名字通常在外部类作用域内,意味着嵌套类的名字通常是Outer::Inner的形式
    (2)访问权限: 嵌套类可以访问外部类的所有成员,包括private成员。外部类也可以访问嵌套类的私有成员,因为嵌套类被视为外部类的成员。
    (3)静态性: 嵌套类可以是静态或者非静态的,与普通类一样。静态嵌套类的实例不依赖于外部类的实例。
  • 嵌套类起着什么作用?
    以下是嵌套类的常见用途:
    (1)封装实现细节
    将一个类的实现细节封装在另一个类中,可以提高代码的可维护性和可读性。外部类可以将一些私有实现细节隐藏在嵌套类中,使外部接口更加简洁;
    (2)逻辑组织
    嵌套类可以用于逻辑上组织代码。当一个类的功能被细分为几个逻辑上相关的部分,将这些部分定义为嵌套类有助于阻止代码结构。
    (3)名称空间管理
    在某些情况下,嵌套类可以用于创建类似于名称空间的的结构。通过将相关的类组织在一个外部的类中,可以减少全局命名空间中的名称冲突。
    (4)实现迭代器和访问器
    嵌套类常常用于实现迭代器和访问器模式。在容器类中嵌套定义一个迭代器类,可以更方便地访问容器的元素。
    (5)实现工厂模式
    在工厂模式中,嵌套类可以用于封装产品的创建细节,使外部类更加注重于高层逻辑。
    (6)提高可读性
    当一个类的功能,与其他功能关系密切,但并不适合于作为整个类的成员时,可以考虑将其定义为嵌套类,从而提高代码的可读性。
    以下是一个栗子:
#include
class Car{
public:
	//外部公共类接口
	
	void startEngine(){
		//使用嵌套类封装启动引擎的实现细节
		Engine engine;
		engine.activate();
	}
private:
	//	嵌套类,封装启动引擎的实现细节
	class Engine{
	public:
		void activate(){
		std::cout << "Engine activated." << std::endl;
		//具体的启动引擎的细节
		}
	};	
};
int main(){
	Car myCar;
	myCar.startEngine();
	return 0;
}

输出

Engine activated.
#include
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)使用内部代理类

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

2、总结:

书山有路勤为径,学海无涯苦作舟。

3、参考

3.1《More Effective C++》

你可能感兴趣的:(#,《More,Effective,C++》,c++)