【Effective C++】条款45: 运用成员函数模板接受所有兼容的类型

假设有如下继承结构:

class Top{};
class Middle: public Top{};
class Bottom: public Middle{};

public继承意味着is-a关系,所有的基类都是派生类,但反之则不是,例如所有的学生都是人,但不是所有的人都是学生.

派生类到基类的指针可以直接隐式转换

Top* pt1 = new Middle;
Top* pt2 = new Bottom;
const Top* pct2 = pt1;
Bottom* pb1 = new Top; // ERROR,无法向上转型

但假设,我们写了一个智能指针类,当此智能指针的模板参数是这些类的时候,如何才能实现上述继承结构下的隐式转换呢?
假设要实现以下功能:

template<typename T>
class SmartPtr{
public:
	explicit SmartPtr(T* realPtr):ptr(realptr){
		...
	}
	T* get() const{
		return ptr;
	}
private:
	T* ptr;
	size_t count;
};

SmartPtr<Top> pt1 = SmartPtr<Bottom>(new Bottom);  //直接隐式转换
SmartPtr<Bottom> pb1 = SmartPtr<Top>(new Top);  //倒反天罡,拒绝此转换并甩出一个ERROR

要知道,如果你不显式的实现此功能,那么SmartPtr<Top>SmartPtr<Bottom>只是毫不相干的两个类罢了,当这两个类赋值的时候,肯定不可以直接隐式转换.,分析上面的需求,可以发现,这个功能其实是这样的:

SmartPtr<Top> pt1 = SmartPtr<Bottom>(new Bottom);
其实就是
SmartPtr<Top> pt1(SmartPtr<Bottom>(new Bottom));  别被这里的=号迷惑了,这是调用构造函数而不是调用=操作函数
1. 调用SmartPtr<Bottom>(new Bottom)构造函数构造出SmartPtr<Bottom>对象来
2. SmartPtr<Top> pt1调用拷贝构造函数接受SmartPtr<Bottom>对象,然后构造出SmartPtr<Top>对象来

经过分析,可以发现,关键点在于拷贝构造函数,只要拷贝构造函数能复用编译器关于类型向上/向下,显式/隐式的转换规则,那我们的SmartPtr就可以模拟上面提到的类型转换.
所以可以这样写:

template<typename T>
class SmartPtr{
public:
	explicit SmartPtr(T* realPtr):ptr(realptr){
		...
	}
	
	T* get() const{
		return ptr;
	}
	
	template<typename U>
	SmartPtr(const SmartPtr<U>& other):ptr(other.get()){
		// 使用列表初始化直接赋值,也可以在函数体赋值
		// 当赋值时就会触发编译器的类型转换,并抛出对应的警告或错误,亦或者可以直接赋值或隐式转换.
		...
	}
private:
	T* ptr;
	size_t count;
};

这样就算解决了80%,还有一个坑在这里.
当我们使用了函数模板兼容了所以的类型后,如果模板类型参数T和U的类型相同,例如

SmartPtr<int> pi1 = SmartPtr<int>(new int);

此时两个对象的类型都相同,都是SmartPtr<int>,注意,模板参数int也是此类型的一部分.
那么编译器有两种选择,一个就是隐式生成默认拷贝构造函数然后调用,二个就是实例化拷贝构造函数模板然后调用,经过实际测试,类型都相同的情况下,编译器(gcc9.4.0)只会调用自己隐式生成的拷贝构造函数,并不会实例化拷贝构造函数模板,所以如果此问题想完美解决,还要手动自定义默认拷贝构造函数,例如std::shared_ptr就有两个拷贝构造函数:
两个拷贝构造函数
测试Demo:

#include <iostream>

template<typename K>
class Test{
    //int&& rvalue_ref = 0;  // c++11起,右值引用会抑制编译器生成默认构造函数
public:
    Test(){
        printf("%s\n",__PRETTY_FUNCTION__);
    }

    template<typename T>
    Test(const Test<T>& other){
        printf("%s\n",__PRETTY_FUNCTION__);
    }

    Test(const Test& other){  // 手动定义的拷贝构造函数
    	printf("%s\n",__PRETTY_FUNCTION__);
    }

};

int main() {
    Test<int> t1 = Test<double>();
	printf("---------------------\n");
    Test<int> t2;
    Test<int> t3 = t2;  // 会调用手动定义的拷贝构造函数
    // 如果无手动定义的拷贝构造函数,则调用编译器定义的拷贝构造函数
    // 如果抑制生成了编译器的拷贝构造函数,则宁报错也不会实例化拷贝构造函数模板
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值