Effective C++: Item 45 -- Use member function templates to accept “all compatible types.”

SmartPtr example

Smart pointers are objects that act much like pointers but add functionality pointers don’t provide. However, one of the things that real pointers do well is support implicit conversions.

To emulating such conversions in user-defined smart pointer classes is tricky. We’d need the following code to compile:

template<typename T> 
class SmartPtr {
public:								// smart pointers are typically
	explicit SmartPtr(T *realPtr); 	// initialized by built-in pointers
	... 
};
SmartPtr<Top> pt1 = // convert SmartPtr<Middle> ⇒
	SmartPtr<Middle>(new Middle);  	// SmartPtr<Top>
SmartPtr<Top> pt2 =   				// convert SmartPtr<Bottom> ⇒ 
	SmartPtr<Bottom>(new Bottom); 	// SmartPtr<Top>
SmartPtr<const Top> pct2 = pt1;		// convert SmartPtr<Top> ⇒
									// SmartPtr<const Top>

Problem:

There is no inherent relationship among different instantiations of the same template, so compilers view SmartPtr<Middle> and SmartPtr<Top> as completely different classes.
A key observation is that there is no way to write out all the constructors we need. In the hierarchy above, we can construct a SmartPtr<Top> from a SmartPtr<Middle> or a SmartPtr<Bottom>, but if the hierarchy is extended in the future, SmartPtr<Top> objects will have to be constructible from other smart pointer types.

Solution

In principle, the number of constructors we need is unlimited. Since a template can be instantiated to generate an unlimited number of functions, it seems that we don’t need a constructor function for SmartPtr, we need a constructor template. Such templates are examples of member function templates (often just known as member templates) — templates that generate member functions of a class:

template<typename T> 
class SmartPtr {
public:
	template<typename U>  					// member template
	SmartPtr(const SmartPtr<U>& other); 	// for a ”generalized
	...										// copy constructor”
};

Constructors like this — ones that create one object from another object whose type is a different instantiation of the same template (e.g., create a SmartPtr<T> from a SmartPtr<U>) — are sometimes known as generalized copy constructors.

The generalized copy constructor above is not declared explicit. That’s deliberate. Type conversions among built-in pointer types (e.g., from derived to base class pointers) are implicit and require no cast, so it’s reasonable for smart pointers to emulate that behavior.

Problem:

We want to be able to create a SmartPtr<Top> from a SmartPtr<Bottom>, but we don’t want to be able to create a SmartPtr<Bottom> from a SmartPtr<Top>, as that’s contrary to the meaning of public inheritance (see Item 32). we also don’t want to be able to create a SmartPtr<int> from a SmartPtr<double>, because there is no corresponding implicit conversion from double* to int*.

Solution:

Assuming that SmartPtr follows the lead of auto_ptr and tr1::shared_ptr by offering a get member function that returns a copy of the built-in pointer held by the smart pointer object (see Item 15), we can use the implementation of the constructor template to restrict the conversions to those we want:

template<typename T> 
class SmartPtr {
public:
	template<typename U> 
	SmartPtr(const SmartPtr<U>& other) 	// initialize this held ptr
	: heldPtr(other.get()) { ... }		// with other’s held ptr
	T* get() const { return heldPtr; }
	...
private:								// built-in pointer held 
	T *heldPtr;							// by the SmartPtr
};

This will compile only if there is an implicit conversion from a U* pointer to a T* pointer, and that’s precisely what we want.

Another Common Role – support for assignment

For example, TR1’s shared_ptr (again, see Item 13) supports construction from all compatible built-in pointers, tr1::shared_ptrs, auto_ptrs, and tr1::weak_ptrs (see Item 54), as well as assignment from all of those except tr1::weak_ptrs.

template<class T> 
class shared_ptr { 
public:
	template<class Y>
		explicit shared_ptr(Y * p);						// construct from
	template<class Y> 									// any compatible
		shared_ptr(shared_ptr<Y> const& r);				// built-in pointer,
	template<class Y>									// shared_ptr,
		explicit shared_ptr(weak_ptr<Y> const& r);		// weak_ptr, or 
	template<class Y>									// auto_ptr
		explicit shared_ptr(auto_ptr<Y>& r);
		
   template<class Y>									// assign from
		shared_ptr& operator=(shared_ptr<Y> const& r);	// any compatible 	
	template<class Y>									// shared_ptr or 
		shared_ptr& operator=(auto_ptr<Y>& r);			// auto_ptr
	... 
};

All these constructors are explicit, except the generalized copy constructor. That means that implicit conversion from one type of shared_ptr to another is allowed, but implicit conversion from a built-in pointer or other smart pointer type is not permitted.

auto_ptrs passed to tr1::shared_ptr constructors and assignment operators aren’t declared const, in contrast to how the tr1::shared_ptrs and tr1::weak_ptrs are passed. That’s a consequence of the fact that auto_ptrs stand alone in being modified when they’re copied.

Note

Declaring a generalized copy constructor (a member template) in a class doesn’t keep compilers from generating their own copy constructor (a non- template), so if you want to control all aspects of copy construction, you must declare both a generalized copy constructor as well as the “normal” copy constructor.

template<class T> 
class shared_ptr { 
public:
	shared_ptr(shared_ptr const& r);					// copy constructor
	template<class Y> 									// generalized
		shared_ptr(shared_ptr<Y> const& r);				// copy constructor
	shared_ptr& operator=(shared_ptr const& r);			// copy assignment
	template<class Y>									// generalized
		shared_ptr& operator=(shared_ptr<Y> const& r);	// copy assignment
	... 
};

Things to Remember

  • Use member function templates to generate functions that accept all compatible types.
  • If you declare member templates for generalized copy construction or generalized assignment, you’ll still need to declare the normal copy constructor and copy assignment operator, too.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值