C++之父Bjarne谈C++的未来发展
富有活力的语言需要不断改变和成长,C++也不例外。在本文中,Bjarne Stroustrup提出了自己对C++的设计和演化的看法。
为了让编译器、工具和类库实现者跟上节奏,让用户吸收标准C++所支持的编程技术,在早有预计的、沉寂了几年之后,委员会再次考虑语言扩展问题了。"扩展工作组"已经建立了,它代替了"演化工作组"。名称的改变(这是Tom Plum的建议)反映了更重要的是语言特性和标准类库工具的集成。我仍然是该工作组的主席。我希望这可以确保C++版本的连贯性和最终结果的一致性。相似的,委员会成员资格也显示了大量人员和组织的连续参与。幸运的是,也出现了很多新的面孔,为委员会带来了新的影响和新的专家意见。
我们打算对语言本身的改变保持谨慎和保守,重点强调兼容性。主要的目的是把主要的努力引导到标准类库的扩展上来。在标准类库方面,我们的目标是大胆进取,利用一切机会。
对于标准类库,我希望根据类库技术报告的要素来建立它,使它成为一个用于系统编程的更广泛的平台。例如,我希望看到用于某些领域的类库,例如目录/文件夹操作、线程和套接字。我还希望委员会同情很多新的C++程序员,提供类库工具支持背景不同的新手(不是新程序员和C的难民)。例如,我希望看到一个使用范围检查STL的标准方法。我对最频繁地被请求添加到标准类库中的标准GUI(图形用户接口)的期望值很低。但是,奇迹有时候也会发生--记得STL吗?
对于语言本身,我希望重点强调支持泛型编程的特性,因为泛型编程是语言的使用取得最大进步的领域。此处,我将调查两个关键部分:
·概念(Concepts):用于模板参数的类型系统
·初始化器(Initializer)列表:初始化工具的泛化
与以往一样,建议的数量仍然远远超出了委员会能够处理和该语言能够吸收的数量。请记住,接受所有好的建议是不可能办到的。
该语言扩展以支持泛型编程的全部目标是为工具提供更大的一致性,允许我们用泛型直接表示用于解决问题的类。
我的其它优先考虑(与更好地支持泛型编程一起)是更好地支持初学者。目前的建议有一种值得注意的倾向,即这些建议照顾了哪些提出和评估建议的专家用户。有些简单地帮助那些新手的建议经常被忽略了。我认为这是一种潜在的致命的设计偏好。除非新手受到了充分的支持,否则只有很少人能够成为专家。此外,很多人并不希望成为专家;他们希望仍然是"偶然的C++用户"。例如使用C++进行物理计算或控制试验设备的物理学家只有有限的学习编程技术的时间。计算机专家可能会在编程技术方面花费很多时间,而不仅仅是期望。我们必须消除那些采用优良技术的不必要的障碍。
一个非常简单的例子如下:
vector<vector<double>> v;
在98年的C++中,这会导致语法错误,因为>>是一个单独的词汇记号,而不是封闭模板参数列表的两个>。V正确的声明可能是:
vector< vector<double> > v;
我把它看作是一种阻碍。我曾经建议这个问题值得解决,但是当前的规则和演化工作组用一些很好的理由两次拒绝了我的建议。但是,这些理由都是语言技术方面的,而新手(包括其他语言的专家)没有兴趣。不接受第一种(也是十分)明显的v声明浪费了用户和教师的时间。我希望>>问题和其它相似的"阻碍"不要再出现在C++0x中。实际上,我与Francis Glassborow和其他人一起,正在试图系统地消除最频繁发生的这类"阻碍"。
另一个"阻碍"是:使用默认的复制操作(构造或赋值)来复制带有用户自定义析构函数的类对象是合法的。在这种情况下,要求用户自定义的复制操作将消除大量的、与资源管理相关的麻烦错误。例如,考虑下面这个过度简单化的字符串类:
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;
}
在构造s2之后,s1.p 和 s2.p指向相同的内存区域,而这块内存被删除了两次,可能导致灾难性的后果。这个问题对于经验丰富的C++程序员来说是很明显的,他们一般会提供适当的复制操作或禁止复制。但是,这个问题会严重地困扰新手,破坏其对语言的信任。
禁止带有指针成员的类对象的默认复制行为可能更好,但是这会导致令人厌烦的兼容性问题。修补长期存在的问题的难度比表面看起来要复杂很多,特别是在考虑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;
}
在上面的代码中,我们对In和Predicate类型作出了很多假设。从代码中我们可以看出,不知什么缘故,In必须用适当的语义支持!=、* 和++,并且我们必须能够把In对象复制为参数和返回值。类似的,我们可以看到,我们可以调用一个Pred,其参数是从In返回的任何类型的*(取值操作符),并给结果应用了!操作符,这个结果可以被当作是布尔型的。但是,在代码中所有的这些都是隐含的。标准类库仔细地记载转发迭代子(例子中的In)和谓词(Pred)的这些需求,但是编译器是不会阅读手册的。试试下面的错误,看你的编译器显示的错误信息:
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; }
};
上面的代码定义了一个类,只有当T是一个转发迭代子的时候,它才能编译。但是,Forward_iterator对象没有做任何实际的事务,因此编译器只能(并且的确是)对这种对象做微乎其微的优化操作。我们可以在如下所示的定义中使用Forward_iterator:
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;
}
Alex Stepanov和Jeremy Siek做了很多工作来开发和普及这种技术。他们使用这种技术的一个地方是Boost类库,但是目前你会在大多数标准类库实现中发现约束类。在错误消息的质量方面,它们的差异是很大的。
但是约束类最多是一个不完整的解决方案。例如,在定义中进行测试--如果检查工作只能在声明中完成,那么就会好很多。使用这种方式的时候,我们必须遵循接口的使用规则,并且可以开始考虑真正的模板分开编译的可能性问题。
因此,让我们告诉编译器我们所期望的模板参数:
template<Forward_iterator In, Predicate Pred>
In find_if(In first, In last, Pred pred);
假设我们能够表示出Forward_iterator和Predicate是什么,那么编译器现在可以不理会它的定义,单独地检查find_if()调用了。这时我们所需要做的工作是为模板参数建立一个类型系统。在现代C++环境中,这种"类型的类型(types of types)"被称为"概念(concepts)"。我们可以通过很多途径来说明这种概念;从现在开始,把它们想作是直接受到语言支持的、拥有更好的语法的约束类。一个概念说明了某种类型必须提供的什么工具,而不是说明它如何提供这些工具。完美的概念(例如<Forward_iterator In>)与数学抽象("对于所有的类型In,In可以被增加、销毁和复制")非常类似,如同最初的<class T>就是数学上的"对于所有的类型T"。
只要给出了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_i
补充:软件开发 , C++ ,