条款45:运用成员函数模板接受所有兼容类型

问题

说的是这么一个问题。
现在要实现一个智能指针类,SmartPtr,可以保存管理对象的地址。比如这么的:

template <typename T>
class SmartPtr {
public :
  explicit SmartPtr(T* p) : ptr(p) {}

private:
  T* ptr;
};

用的时候可以:

class Top {
};
SmartPtr<Top> st1 = SmartPtr<Top>(new Top());

析构就不实现了,这个条款讨论的和资源管理无关。

既然是个指针,就要满足裸指针中,子类到基类多态继承的功能。比如在std中的智能指针就可以完成如下操作:

#include <iostream>
#include <memory>

#define SmartPtr std::shared_ptr

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

int main() {
  SmartPtr<Top> st1 = SmartPtr<Bottom>(new Bottom());
  SmartPtr<Top> st2 = SmartPtr<Middle>(new Middle());
  SmartPtr<const Top> st3 = st1;
  
  std::cout << "hello" << std::endl;
}

也就是从子类Bottom、Middle指针到基类Top指针的转换。
然而上面的实现,是做不到的,编译如下代码会报错:

template <typename T>
class SmartPtr {
public :
  explicit SmartPtr(T* p) : ptr(p) {}

private:
  T* ptr;
};

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

int main() {
  SmartPtr<Top> st1 = SmartPtr<Bottom>(new Bottom());
  SmartPtr<Top> st2 = SmartPtr<Middle>(new Middle());
  SmartPtr<const Top> st3 = st1;
}

解决一

为什么不能转呢?因为SmartPtr没有一个构造函数,可以接收SmartPtr<Bottom>的。
对于裸指针,Bottom*和Middle*可以隐式转换成Top*,但是SmartPtr<Bottom>却没法隐式转换成Top。
因此,需要增加一系列构造函数,让子类可以转成基类:

#include <iostream>

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

template <typename T>
class SmartPtr {
public :
  explicit SmartPtr(T* p) : ptr(p) {}
  SmartPtr(const SmartPtr<Bottom>& b) {
    ptr = b.get();
  }
  SmartPtr(const SmartPtr<Middle>& m) {
    ptr = m.get();
  }
  SmartPtr(const SmartPtr<Top>& t) {
    ptr = t.get();
  }

  T* get() const {
    return ptr;
  }

private:
  T* ptr;
};

int main() {
  SmartPtr<Top> st1 = SmartPtr<Bottom>(new Bottom());
  SmartPtr<Top> st2 = SmartPtr<Middle>(new Middle());
  SmartPtr<const Top> st3 = st1;
  std::cout << "end" << std::endl;
}

这样就编译通过了。不过,这是一种笨方法。随着迭代,可能有很多子类,每次新增都需要修改SmartPtr模板来新增构造。这显然是不现实的。毕竟这SmartPtr要当做一个库提供给用户的,怎么可能绑定用户代码,还能让用户频繁修改。

解法二

对拷贝构造函数使用成员函数模板。这样SmartPtr可以接收SmartPtr<U>类型的各种入参。

#include <iostream>
#include <string>

template <typename T>
class SmartPtr {
public :
  explicit SmartPtr(T* p) : ptr(p) {}
  template <typename U>
    SmartPtr(const SmartPtr<U>& other) {
      ptr = other.get();
      std::cout << "before convert:" << other.get()->name_ << std::endl;
      std::cout << "after convert:" << ptr->name_ << std::endl;
    }

  T *get() const {
    return ptr;
  }

private:
  T* ptr;
};

class Top {
public:
  Top () {}
  Top (std::string name) : name_(name) {
    std::cout << "Top:" << name_ << std::endl;
  }
  std::string name_ = "I'm Top";
};
class Middle : public Top {
public:
  Middle() {}
  Middle (std::string name) : name_(name) {
    std::cout << "Middle:" << name_ << std::endl;
  }
  std::string name_;
};
class Bottom : public Middle {
public:
  Bottom() {}
  Bottom (std::string name) : name_(name) {
    std::cout << "Bottom:" << name_ << std::endl;
  }
  std::string name_;
};

int main() {
  SmartPtr<Top> st1 = SmartPtr<Bottom>(new Bottom("b1"));
  SmartPtr<Top> st2 = SmartPtr<Middle>(new Middle("m1"));
  SmartPtr<const Top> st3 = st1;
}

以上实现有以下几个细节需要注意。

  • 没有explicit。因为这里需要隐式转换来完成从子类到基类的转换,所以不能加explicit。
  • 拷贝构造里,将裸指针进行了转换,只有能转换的才会转换成功(这不是屁话吗)。比如子类能转换成基类,基类不能转换成子类等,等于是让编译器做了检查。

另一个问题

  • 如果编译器发现类中没有定义拷贝构造函数,会自动生成一个拷贝构造函数(包括左值和右值,不过在这个问题里不重要)。
  • 泛化拷贝构造函数中,如果T和U一样的时候,就变成了一个正常的拷贝构造函数。但模板的特性是,只有在你有一段代码是具现化了一个实例,它的T和U一样的时候,才会去生成一个对应的拷贝构造函数。
    所以,书里问了一个问题,就说,这两个生成规则会不会互相干扰。
    书上结论是不干扰。如果没有具现化一个U=T的拷贝构造函数,编译器就会自动生成一个。如果想要自己实现,也可以自己定义实现一个拷贝构造函数。
    那这样,自己实现的拷贝构造,和模板具现化之后的,会不会重复定义了呢?
#include <iostream>
#include <string>

template <typename T>
class SmartPtr {
public :
  explicit SmartPtr(T* p) : ptr(p) {}
  explicit SmartPtr(const SmartPtr& other) {
    std::cout << "copy constractor:" << other.get()->name_  << std::endl;
    ptr = other.get();
  }
  template <typename U>
    SmartPtr(const SmartPtr<U>& other) {
      std::cout << "template copy constractor:" << other.get()->name_  << std::endl;
      ptr = other.get();
    }

  T *get() const {
    return ptr;
  }

private:
  T* ptr;
};

class Top {
public:
  Top () {}
  Top (std::string name) : name_(name) {
    std::cout << "Top:" << name_ << std::endl;
  }
  std::string name_ = "I'm Top";
};

int main() {
  SmartPtr<Top> t1 = SmartPtr<Top>(new Top("top1"));
  SmartPtr<Top> t2 = t1;
}

这个demo输出:

Top:top1
template copy constractor:top1
template copy constractor:top1

完全没有调用自己写的拷贝构造函数。这个问题,不太清楚为什么。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值