这一章主要介绍了如何利用模板拷贝构造函数和模板赋值构造函数来最大化的重用一个容器。书上给了一个实现固定大小的vector的例子,代码如下:
template<typename T, size_t size>
class fixed_vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//空的构造函数
fixed_vector(){}
//模板拷贝构造函数
template<typename U, size_t usize>
fixed_vector(const fixed_vector<U, usize>& other)
{
copy(other.begin(),
other.begin() + min(size, usize),
begin());
}
//模板赋值构造函数
template<typename U, size_t usize>
fixed_vector<T, size>&
operator=(const fixed_vector<U, usize>& other)
{
copy( other.begin(),
other.begin + min(size, usize),
begin());
return *this;
}
iterator begin() { return v_; }
iterator end() { return v_ + size; }
const_iterator begin() const { return v_; }
const_iterator end() const { return v_ + size; }
private:
T v_[size];
}
利用模板拷贝构造函数和模板赋值构造函数可以将不同类型的fixed_vector<T>
和 fixed_vector<U>
进行赋值,如果T和U是可以相互转换的话。但这里要注意一个问题,模板拷贝构造函数和赋值拷贝构造函数与拷贝构造函数和赋值构造函数是不同的。也就是说当你写了模板形式的拷贝构造函数和赋值构造函数之后,编译器仍然会隐式的生成默认的拷贝构造函数和赋值构造函数。也就是说当两个内部类型一致的fixed_vector
进行赋值时,是不会调用到模板形式的拷贝构造函数和赋值构造函数的。也就是说T和U不能是同一个类型。
另外,书中提到了stl中实现此类模板拷贝构造和赋值构造的方式。STL中使用的iter range的方式,如下:
template<class RAIter>
fixed_vecotr(RAIter first, RAIter last)
{
copy(first,
first + min(size, (size_t)last - first),
begin());
}
template<class Iter>
fixed_vector<T, size>&
assign(Iter first, Iter last)
{
copy(first,
first + min(size, (size_t)last - first),
begin());
return *this;
}
最后一点,也是蛮重要的一点,就是异常安全问题。上面的fixed_vector
类中的模板拷贝构造和模板赋值构造都不是异常安全的。首先要在赋值构造函数中实现异常安全有一个常用的方法就是使用Temp-Swap方法,就是先利用传进来的另一个对象,构造一个临时的对象,然后把临时对象的内容和此对象的内容进行交换,并且要求这个交换函数是不能抛出异常的。这样的话,哪怕在构造临时对象时发生了异常,也不会改变此对象的状态信息。以上类的设计,无论怎么设计swap函数,也就是交换函数,都无法保证不抛出异常,因为swap过程中肯定伴随着T类型对象的构造,而T类型对象的构造是无法保证不抛出异常的。所以需要修改以上类的设计,把T数组类型的私有变量改成一个指针,这样swap函数只需要交换这个指针就可以了,而交换一个指针是可以保证不抛出异常的。具体实现如下:
// A strongly exception-safe version:
//
template<typename T, size_t size>
class fixed_vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
fixed_vector() : v_(new T[size]) {}
~fixed_vector() { delete [] v_; }
//同类型的异常安全的拷贝构造函数
fixed_vector(const fixed_vector<T, size>& other)
:v_(new T[size])
{
try
{
copy(other.begin(),
other.end(),
begin());
}
catch(...)
{
delete [] v_;
throw;
}
}
//不同类型异常安全的拷贝构造函数
template<typename U, size_t usize>
fixed_vector(const fixed_vector<U, usize>& other)
:v_(new T[size])
{
try
{
copy(other.begin(),
other.begin() + min(size, usize),
begin());
}
catch(...)
{
delete [] v_;
throw;
}
}
//不允许抛出异常的Swap函数
void Swap( fixed_vector<T, size>& other) throw()
{
swap(v_, other.v_);
}
//异常安全的同类型之间的赋值构造函数,利用了Temp-Swap技巧
fixed_vector<T, size>& operator=(
const fixed_vector<T, size>& other)
{
fixed_vector<T,size> temp(other);
Swap(temp);
return *this;
}
//异常安全的不同类型之间的赋值构造函数,利用了Temp-Swap技巧。
template<typename U, size_t usize>
fixed_vector<T, size>& operator=(
const fixed_vector<U, usize>& other)
{
fixed_vector<T, size> temp(other);
Swap(temp);
return *this;
}
iterator begin() { return v_; }
iterator end() { return v_ + size; }
const_iterator begin() const { return v_; }
const_iterator end() const { return v_ + size; }
private:
T* v_;
}
这个类的实现和上面类的实现第一个不同是,这个类是异常安全的。第二个不同是,这个类各多了一个拷贝构造函数和赋值构造函数,这不同于模板拷贝构造函数和模板赋值构造函数。因为如果不添加的话,编译器默认生成的拷贝构造和赋值构造会直接拷贝类内部的指针,这个是完全错误的一种做法。
小结:
通过这一章,要记住以下几点:
1. 拷贝构造函数、赋值构造函数、模板拷贝构造函数、模板赋值构造函数的区别。
2. 如何实现一个异常安全的类。Temp-Swap的方式到底是如何实现的。