我相信我应该混合使用C和C ++代码。这是一个问题,如何纠正?


10

背景/场景

我开始纯粹用C语言编写CLI应用程序(我的第一个适当的C或C ++程序不是“ Hello World”或其变体)。大约在中途,我正在处理用户输入(字符串数组)的“字符串”,然后发现了C ++字符串流对象。我看到我可以使用这些代码保存代码,因此我在应用程序中使用了它们。这意味着我已将文件扩展名更改为.cpp,现在使用g++而不是编译应用程序gcc。因此,基于此,我可以说该应用程序从技术上讲是一个C ++应用程序(尽管90%以上的代码是用我称为C的代码编写的,因为鉴于我有限的经验,这两种语言之间存在很多交叉之处他们俩)。它是一个大约900行长的.cpp文件。

重要因素

我希望该程序免费(如金钱形式),可以自由分发并可供所有人使用。我担心的是,有人会看一下代码,然后想出以下效果:

哦,看一下编码,太糟糕了,这个程序帮不了我

可能的话!另一个问题是代码高效(这是用于测试以太网连接性的程序)。代码中不应存在效率低下的部分,以至于它们可能严重阻碍应用程序或其输出的性能。但是,当寻求有关特定功能,方法,对象调用等的帮助时,我认为这是Stack Overflow的问题。

我的问题

(在我看来)混合了C和C ++,也许我不应该这样做。我应该用C ++重写所有内容(通过这种方式,我的意思是实现更多的C ++对象和方法,也许我已经用C风格编写了可以使用较新的C ++技术浓缩的内容),或者删除了使用字符串流对象和把这一切“带回” C代码?这里有正确的方法吗?我迷路了,需要一些指导如何使该应用程序在群众眼中保持“良好”,因此他们将使用它并从中受益。

代码-更新

是代码的链接。大约有40%的评论,我几乎每行评论,直到我感到更加流利。在我链接到的副本中,我删除了几乎所有注释。我希望这不会使阅读变得困难。我希望没有人需要完全理解它。但是,如果我犯了致命的设计缺陷,我希望它们应该易于识别。我还应该提到,我正在编写几个Ubuntu台式机和笔记本电脑。我无意将代码移植到其他操作系统。


3
我为您消除了CLI的歧义。CLI还可以引用公共语言基础结构,从C的角度来看,这没有多大意义。
罗伯特·哈维

您可以用FORTRAN重写它。我从未听说过OOF。
ott-- 2013年

2
如果您的软件不是“漂亮的”,我不会为人们不使用它而担心。只要您的代码完成了他们需要做的事情,您99%的用户就不会看代码或关心代码的编写方式。但是,保持代码的一致性等非常重要,以帮助长期维护它。
Evicatos 2013年

1
您为什么不使用例如github上的代码制作免费的东西(例如,获得GPLv3许可)。您的代码缺少文件。您可能会得到有趣的反馈。LICENSE
Basile Starynkevitch

Answers:


13

让我们从头开始:C和C ++混合代码相当普遍。所以您首先要在一个大俱乐部中。我们有大量的C代码库。但是出于明显的原因,许多程序员拒绝使用C编写至少新的东西,而可以在同一编译器中访问C ++,所以新的模块开始以这种方式编写-首先只是将现有的部分搁置一旁。

然后最终将一些现有文件重新编译为C ++,并且可以删除某些网桥...但是可能要花很长时间。

您有些领先,您的整个系统现在是C ++,只是其中大部分是“ C风格”的。您会看到样式混合是一个问题,您不应该遇到的问题:C ++是一种支持多种样式的多范式语言,可以使它们永久共存。实际上,这是主要优势,您不必被迫拥有单一风格。到处都是次优的,有些运气并非到处都是。

如果代码库损坏了,那么重新设计代码库是个好主意。或是否正在发展中。但是,如果它可以工作(从最初的意义上来说),请遵循最基本的工程原理:如果它没有损坏,请不要对其进行修复。不用管冷的部分,尽一切努力。在不良,危险的零件上-或具有新功能的零件,只需重构零件以使其成为床即可。

如果您想解决一些一般性的问题,那么就值得从C代码库中逐出:

  • 所有的str *函数和char []-用一个字符串类替换它们
  • 如果使用sprintf,请创建一个版本,该版本返回带有结果的字符串,或将其放入字符串中,并替换用法。(如果您从不烦恼流,请帮忙一个忙,然后跳过它们,除非您喜欢它们; gcc为检查格式提供了开箱即用的完美类型安全性,只需添加适当的属性即可。
  • 大多数malloc和免费-不适用于new和delete,而是vector,list,map和其他收集器。
  • 其余的内存管理(在前两点之后,它一定很少见,用智能指针覆盖或实现您的特殊集合)
  • 替换所有其他资源使用情况(FILE *,互斥量,锁等)以使用RAII包装器或类

完成后,您将达到代码库可以合理地保证异常安全的地步,因此您可以使用异常和仅在高级函数中进行罕见的 try / catch 来放弃返回代码。

除此之外,只需使用一些健康的C ++编写新代码,并且如果诞生了一些可以替换现有代码的类,就可以使用它们。

我没有提到与语法有关的东西,显然在所有新代码中都使用了refs而不是指针,但是仅仅为更改而替换旧的C部分并没有什么价值。您必须进行强制转换,消除所有可能,其余部分在包装函数中使用C ++变体。非常重要的是,在适用的地方添加const。这些与早期的子弹交错。并合并您的宏,并将您可以创建的内容替换为枚举,内联函数或模板。

我建议阅读Sutter / Alexandrescu的C ++编码标准(如果尚未完成的话),并严格遵循它们。


非常感谢您输入Balog,所有声音建议都在我眼中,我完全同意。我将考虑逐步改变我认为的代码,优先考虑工作代码。
jwbensley

7

简而言之:您不会下地狱,不会有任何不好的事情发生,但是您也不会赢得任何选美比赛。

虽然完全有可能将C ++用作“更好的C”,但是您将放弃许多C ++的好处,但是由于您并不局限于香草C,因此无法获得C的任何好处(简单,可移植性) ,透明度,互操作性)。换句话说:C ++牺牲了C的某些特质来获取其他特质-例如,C的透明性使您始终可以清楚地看到内存分配发生的时间和地点,并将其换成了C ++的更强大的抽象。

由于您的代码现在看来可以正常工作,因此,仅出于此目的而重写它可能不是一个好主意:现在就将其保持原样,然后将其一次更改为更多惯用的C ++,每当您在任何给定的零件上工作时。并牢记在下一个项目中所汲取的教训,这是其中的大部分:C和C ++不是同一种语言,与决定在项目中途切换到C ++相比,最好事先做出选择。


感谢tdammers的建议。我同意您所说的内容,现在接受了,谢谢!
jwbensley

4

从某种意义上说,C ++是一种非常复杂的语言。它也是一种支持多种范例的语言,这意味着它使您无需使用任何对象即可编写完整的过程代码。

因此,由于C ++非常复杂,因此您经常看到人们在其代码中使用其功能的某些有限子集。初学者通常只使用流I / O,字符串对象和new / delete代替malloc / free,并且可能使用引用代替指针。当您了解面向对象的功能时,您可能会开始以一种名为“带有类的C”的样式进行编写。最终,随着您对C ++的更多了解,您开始使用模板,RAIISTL,智能指针等。

我要说的是,学习C ++是一个需要时间的过程。是的,现在您的代码可能看起来像是由试图编写C ++的C程序员编写的。而且由于您只是在学习,所以完全可以。但是,当我在经验丰富的程序员(应该更了解)的生产代码中看到这种情况时,这确实让我感到畏缩。

请记住,一个好的Fortran程序员可以用任何语言编写好的Fortran代码。:)


2

听起来您正在准确地概括许多老C程序员在使用C ++时所采用的路径。您将其用作“更好的C”。二十年前,这是有道理的,但是在这一点上,C ++中有许多更强大的构造(例如标准模板库),现在几乎没有意义了。至少,您可能应该执行以下操作,以避免在查看代码时给C ++程序员带来动脉瘤:

  • 使用流而不是?printf家庭。
  • 使用new代替malloc
  • 将STL容器类用于所有数据结构。
  • 尽可能使用std::string代替char*
  • 了解和使用RAII

如果您的程序很短(似乎很短,大约900行),我个人认为创建一个健壮的类集不是必需的,甚至没有用。


感谢Steven的建议,是的,我对类有相同的想法,我认为我可以将代码整理到一个整齐的单个文件中。谢谢!
jwbensley

2

接受的答案仅提及将C转换为惯用C ++的优点,好像C ++代码在某种绝对意义上要比C代码更好。我同意其他答案,如果没有破坏混合代码,则可能无需进行任何大的更改。

混合C和C ++是否可持续,取决于混合方式。例如,当在C代码中引发异常(这不是异常安全的)时,可能会引入细微的错误,从而导致内存泄漏或数据损坏。比较安全和通用的方法是将C库或接口包装在类中,或者在C ++项目中以其他某种隔离方式使用它们。

在决定将整个项目重写为惯用的C ++(或C)之前,您应该意识到,其他答案中提供的许多更改可能会使您的程序变慢或引起其他不良影响。例如:

  • 将堆栈分配的C字符串更改为std :: strings可能导致不必要的堆分配
  • 将原始指针更改为某些共享指针类型(例如std :: shared_ptr)会由于引用计数,内部虚拟成员函数和线程安全性而导致访问开销
  • 标准库流比C库慢
  • 随意使用RAII类(如容器)可能会导致不必要的操作,尤其是在无法使用C ++ 11的移动语义的情况下
  • 模板可能导致更长的编译时间,不清楚的编译错误,代码膨胀以及编译器之间的可移植性问题
  • 编写异常安全代码很困难,这使得在转换过程中轻松引入细微的错误
  • 使用共享库中的C ++代码比普通C语言更容易
  • C ++不如C89受到广泛支持

总而言之,从混合的C和C ++代码转换为惯用的C ++应该被视为一种权衡,这不仅是实现时间,而且还具有看似方便的功能扩展了您的工具箱。因此,除了“取决于”之外,很难为一般情况提供答案。


“标准库流比C语言慢”:可能的是,无论如何国际化都比printf难。
重复数据删除器

1

混合使用C和C ++是不好的形式。它们是不同的语言,确实应该这样对待。选择最适合的一种,然后尝试用该语言编写惯用代码。

如果C ++为您节省了大量代码,请坚持使用C ++并重新编写C部分。我怀疑如果这是您所关注的,性能差异是否会变得明显。


因此,我有一个要使用的用C语言编写的库,而我有另一个我要使用的用C ++语言编写的库...重写工作得很好的代码只是在创建不必要的工作。
gnasher729

1

我一直在C和C ++之间来回走动,是喜欢这种方式工作的奇怪类型。我更喜欢C ++进行高级处理,而我将C用作钝器,可以对X射线数据类型进行处理,并像对待位和字节一样对待它们,例如,memcpy对于实现低级数据结构和内存分配器我很方便。如果您发现自己在原始位和字节级别上工作,那么C ++真正丰富的类型系统根本无法提供任何帮助,而且我经常发现用C编写这样的代码更容易,在此我可以放心地假设我可以将任何C数据类型都视为位和字节。

要记住的主要事情是,这两种语言的结合不太好。

1. AC函数永远不要调用可以使用的C ++函数throw。考虑到您日常的C ++代码可以隐式地遇到异常的地方很多,这通常意味着您的C函数一般不应调用C ++函数。否则,您的C代码将无法释放在堆栈展开期间分配的资源,因为它没有捕获C ++异常的实用方法。例如,如果最终确实需要C代码将C ++函数作为回调来调用,则C ++方面应确保在返回C代码之前捕获到它遇到的任何异常。

2.如果编写的C代码将数据类型仅视为原始位和字节,推翻了类型系统(这实际上是我首先使用C的主要原因),那么您永远都不想对C ++数据使用此类代码可能具有复制构造函数和析构函数以及虚拟指针的类型,以及此类类型的东西都需要加以尊重。因此,通常应该是使用此类C代码的C代码,而不是使用此类C代码的C ++代码。

如果您希望您的C ++代码使用此类C代码,那么通常您希望将其用作类的实现细节,该类可确保其存储在通用C数据结构中的数据是构造起来很简单的数据类型。并摧毁。幸运的是,可以使用诸如此类的类型特征来检查静态断言中的类型特征,确保要存储到通用C数据结构中的类型具有琐碎的析构函数和构造函数,并且将保持这种状态。

我应该用C ++重写所有内容(通过这种方式,我的意思是实现更多的C ++对象和方法,也许我已经用C风格编写了可以使用较新的C ++技术浓缩的内容),或者删除了使用字符串流对象和把这一切“带回” C代码?

在我看来,如果您遵守上述规则并确保您的代码已针对编写的测试进行了良好的测试,则无需打扰。到目前为止,可能值得改进的部分是您用C ++编写的代码,但这并不意味着您必须将严格基于C的代码移植到C ++。

使用您用C ++编写并作为C ++代码进行编译的代码,您必须要小心。在C ++中使用类似C的编码会带来麻烦。例如,如果您手动分配和释放资源,则您的代码可能不是异常安全的,因为您的代码可能会遇到异常,此时您将隐式退出该功能,然后才能使用free该资源。在C ++中,RAII和诸如智能指针之类的东西并不方便,因为如果您仅查看常规执行路径而不考虑特殊路径,它们可能会出现。它们通常是简单有效地编写正确的异常安全代码的基本必要条件,而不仅仅是在遇到异常时开始在各处泄漏。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.