条款25:考虑写出一个不抛出异常的swap函数
所谓swap两个对象,就是将两个对象的值彼此赋予对方。标准库可能是这样实现的:
namespace std{
template<typename T>
void swap(T& a,T&b)
{
T temp(a);
a = b;
b = temp;
}
}
只要类型T支持copying(copy constructor and copy assignment)就可以了。
“以指针指向一个对象,内含真正的数据”这种表现形式就是“pimpl手法”,以 Widget class 为例:
class WidgetImpl{
public:
...
private:
int a,b,c;
std::vector<double> v;
...
};
class Widget{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
...
*pImpl = *(rhs.pImpl);
...
}
...
private:
WidgetImpl* pImpl;
};
在这里交换Widget对象,只需要交换pImpl指针就可以了,但是default swap算法不仅要复制三个Widget对象还要复制三个WidgetImpl。
so,我们期望这样做
namespace std{
template<>
void swap<Widget>(Widget& a, Widget& b)
{
swap(a.pImpl,b.Impl);//编译无法通过,因为pImpl是private
}
}
我们不允许改变std内的任何空间,但是我们可以为标准template制造特化版本,如上面所示。当然我们可以将此特化版本声明为friend,但是这有违背以往的规矩:我们可以这样做:
class Widget{
public:
...
void swap(Widget& other)
{
using std::swap;
swap(pImpl,other.pImpl);
}
...
};
namespace std{
template<>
void swap<Widget>(Widget& a,Widget& b)
{
a.swap(b);
}
}
然而假设两个类都是template呢:
template<typename T>
class WidgetImpl{...};
template<typename T>
class Widget{...};
在Widget内部的swap函数不变,但是特化会变为:
namespace std{
template<typename T>
void swap< Widget<T> >(Widget& a,Widget& b)
{ a.swap(b); }
}
C++只允许对class template实现偏特化,在function template偏特化是行不通的。我们习惯性的动作是做一个函数重载:
namespace std{
template<typename T>
void swap(Widget<T>& a,Widget<T>& b)
{ a.swap(b); }
}
但是很不幸,C++标准委员会不允许我们对它已经定义好的东西膨胀,所以达不到我们预期的效果。我们可以这样做:
namespace WidgetStuff{
...
template<typename T>
class Widget{...};
...
template<typename T>
void swap(Widget<T>& a,Widget<T>& b)
{
a.swap(b);
}
}
现在我们达到了预期的效果。但是我们希望的是在使用的时候调用T的专属版本,并在该版本不存在的情况下调用std内的版本:
template<typename T>
void doSomething(T& obj1,T&obj2)
{
using std::swap;
...
swap(obj1,obj2);
...
}
这里需要注意错误的调用方式: std::swap(obj1,obj2);这会影响sdt选择函数。