百科问答小站 logo
百科问答小站 font logo



为什么同为系统级编程语言,Rust 能拥有现代构建/包管理工具,C++ 却不能? 第1页

  

user avatar   bombless 网友的相关建议: 
      

这方面C++欠缺的就是一个模块系统。

举例说吧。

假如要做一个C++包管理器,怎么管理不同库之间的依赖呢?

容易想到的一个方案就是每个库都提供一个入口头文件,编译一个项目时由包管理器自己生成一个文件,把编译的项目的这个入口头文件和所依赖的每个库的文件都在生成的这个文件里面#include一次,然后让编译器直接去编译这个生成的文件就好了。

现在你有两个问题。

首先是名字空间冲突的问题。这个好说,只要包管理器统一管理名字,让一个库只能独自占用一个顶级的名字空间就好。

然后,假使你的库是A,依赖B和C两个库,B和C又同时依赖一个库D。那么这样的菱形依赖怎么解决呢?也好办,让包管理器在生成编译文件的时候考虑到顺序,使得D的入口文件的包含总是发生在B和C的前面,并且使每个库只在其中出现一次,否则包管理器报错。

到目前为止事情还好办。

还是用上面的例子,你的库是A,依赖B和C两个库,B和C又同时依赖一个库D。

现在,D有一个类型X,库B和C都对类型X做了全局重载,比如说重载了X+X,这个时候重复了。

这时候包管理器当然要报错。问题是B和C都是独立开发的,这个错要让谁去修复?难道是你,A的作者?

阻碍上面的程序完成构建的规则在C++中称为ODR,One Definition Rule。

在支持全局重载的其他编程语言中,包括Rust,对这种窘况有一个规则叫做coherence,有时候叫做orphan rule。

在上面的例子中,如果换了Rust的情况,类型X归属于crate(指一个编译单元你可以理解为模块)D,+运算符也有归属,是std标准库。这个时候B和C都不允许在X上做X+【随便某个类型】的重载。

如果B的作者需要做类似的事,需要自己包装类型X产生一个新的类型Y,然后在Y上做+运算符的全局重载。

C++就算要去掉全局重载也是很尴尬的,比方说iostream的灵活性就依赖于全局重载。


如果想像Rust(以及其他ML语言)那样,可以在C++加个预处理指令放在编译单元的入口文件的开头,标明对这个编译单元所有的全局重载要遵循orphan rule,同时引入外部模块的概念,这样类型才能所属于某个模块。这样能够在编译的时候报错,而不是像ODR那样在链接的时候报错那就晚了。代价就是iostream肯定没法正常用了。

如果采用像C#那种做法,在需要模块化的代码中去掉全局重载的使用,把运算符重载放在静态成员函数上实现,这样在模板特化方面没法做,还是要引入模块系统,但是不失为另一种做法。




  

相关话题

  为什么说 Java 比 C / C++ 慢? 
  C++ 11为什么引入nullptr? 
  如何看待 .NET Native,真能达到 C++ 的性能、C# 的生产效率吗? 
  如何评论"知名游戏开发者称 C++ 是一种非常糟糕、可怕的语言"? 
  如何评价Qt Lite Project? 
  malloc申请的内存能是虚拟内存吗,也就是申请的一块新的空间,刚申请就缺页吗? 
  Mac系统下有什么适合图形学的C++IDE?Clion还是VS? 
  C++ 有哪些缺点? 
  如何解决 C++ 代码不能打开提示有一个错误的问题? 
  设计模式中策略模式策略类膨胀的问题? 

前一个讨论
如何看待里约奥运闭幕式回顾短片不见中国?
下一个讨论
Windows 的 PowerShell 和 Linux 的 terminal 有啥区别?





© 2025-01-31 - tinynew.org. All Rights Reserved.
© 2025-01-31 - tinynew.org. 保留所有权利