另一个"阻碍"是:使用默认的复制操作(构造或赋值)来复制带有用户自定义析构函数的类对象是合法的。在这种情况下,要求用户自定义的复制操作将消除大量的、与资源管理相关的麻烦错误。例如,考虑下面这个过度简单化的字符串类:
class String {
public:
String(char* pp) :sz(strlen(pp)), p(new char[sz+1]) { strcpy(p,pp); }
~String() { delete[] p; }
char& operator[](int i) { return p[i]; }
private:
int sz;
char* p;
};
void f(char* x)
{
String s1(x);
String s2 = s1;
}
禁止带有指针成员的类对象的默认复制行为可能更好,但是这会导致令人厌烦的兼容性问题。修补长期存在的问题的难度比表面看起来要复杂很多,特别是在考虑C兼容性的时候。
1、概念(Concepts)
D&E(编者注:"C++的设计和演化"通常简称为D&E)关于模板的讨论中包含的关于模板参数的约束问题就占用了整整三页。很明显,我觉得应该需要一个更好的解决方案。在使用模板(例如标准类库的算法)的过程中出现的微小错误所导致的错误消息可能非常长,并且没有对我们没有任何帮助。这个问题是由于模板代码绝对相信自己的模板参数。看看下面的find_if():
template<class In, class Pred>
In find_if(In first, In last, Pred pred)
{
while (first!=last && !pred(*first)) ++first;
return first;
}
find_if(1,5,3.14); // 错误
不完整的、但是十分高效的,以我的旧想法--让构造函数检查模板参数的假设条件--为基础的解决方案现在已经广泛使用了。例如:template<class T> struct Forward_iterator {
static void constraints(T a) {
++a; a++; // 可以增加
T b = a; b = a; // 可以复制
*b = *a; // 可以废弃和复制结果
}
Forward_iterator() { void (*p)(T) = constraints; }
};
template<class In, class Pred>
In find_if(In first, In last, Pred pred)
{
Forward_iterator<In>(); // 检查模板参数类型
while (first!=last && !pred(*first)) ++first;
return first;
}
但是约束类最多是一个不完整的解决方案。例如,在定义中进行测试--如果检查工作只能在声明中完成,那么就会好很多。使用这种方式的时候,我们必须遵循接口的使用规则,并且可以开始考虑真正的模板分开编译的可能性问题。
因此,让我们告诉编译器我们所期望的模板参数:
template<Forward_iterator In, Predicate Pred>
In find_if(In first, In last, Pred pred);
只要给出了find_if()的这种声明(并且不是定义)之后,我们就可以编写
int x = find_if(1,2,Less_than<int>(7));
这个调用会失败,因为int不支持*。换句话说,这个调用在编译时会失败,因为int不是一个Forward_iterator。重要的是,它使得编译器容易报告用户语言中的错误,并且在编译时,调用会被首先看到。不幸的是,知道迭代子参数是Forward_iterator并且谓词参数是Predicate也不足以保证find_if()调用成功编译。这两个参数是互相影响的。特别是谓词的参数是一个使用*(pred(*first))解除引用的迭代子。我们的目的是在与调用分离的情况下,完善模板的检测,同时在不查看模板定义的情况下,完善每个调用的检查,因此概念必须有充分的表现能力,能够处理模板参数之中的这类迭代子。一种办法是用平行的参数来表示概念,这与模板的参数化方式类似。例如:
template<Value_type T,
Forward_iterator<T> In, // 迭代子在T序列中
Predicate<bool,T> Pred> // 带有 T 参数并返回一个布尔值
In find_if(In first, In last, Pred pred);
