从C ++移到C

在用C ++编写了几年之后,我最近在embedded式领域提供了一个用C语言编写的工作。

抛开在embedded式领域中忽略C ++是对还是错的问题,在C ++中有一些特性/习惯用法我会错过很多。 仅举几个:

  • 通用的,types安全的数据结构(使用模板)。
  • RAII。 特别是在具有多个返回点的函数中,例如不必记住在每个返回点上释放互斥量。
  • 一般的破坏者。 即你为MyClass写了一次,然后如果一个MyClass实例是MyOtherClass的一个成员,MyOtherClass不必显式地去初始化MyClass实例 – 它的自动调用。
  • 命名空间。

你从C ++转到C的经验是什么?
你find了什么C代替你最喜欢的C ++function/成语? 你有没有发现你希望C ++的C特性?

在一个embedded式项目上工作,我尝试过所有的C,只是无法忍受。 它太冗长了,所以很难阅读任何东西。 另外,我喜欢我写的优化的embedded式容器,为了修复#define块,它不得不变得更安全和更难。

在C ++中的代码如下所示:

 if(uart[0]->Send(pktQueue.Top(), sizeof(Packet))) pktQueue.Dequeue(1); 

变成:

 if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet))) Queue_Packet_Dequeue(pktQueue, 1); 

很多人可能会说的很好,但是如果你不得不做一两行“方法”的话,那就太可笑了。 两行C ++会变成C中的五行(由于80个字符的行长限制)。 两者都会生成相同的代码,所以它不像目标处理器那样关心!

有一次(早在1995年),我曾经为一个多处理器数据处理程序写了大量的C语言。 每种处理器都有自己的内存和程序。 供应商提供的编译器是一个C编译器(某种HighC衍生产品),它们的库是封闭源代码的,所以我不能使用GCC来编译,而且它们的API的devise思路是你的程序主要是初始化/进程/终止变化,所以处理器间的通信最好是最基本的。

在放弃之前,我花了大约一个月的时间,find了一个cfront的副本,并将其入侵到makefile中,以便我可以使用C ++。 Cfront甚至不支持模板,但C ++代码更清晰得多。

通用的,types安全的数据结构(使用模板)。

C对模板最接近的事情是声明一个头文件,其代码如下:

 TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { /* ... */ } 

然后用类似的东西拉它:

 #define TYPE Packet #include "Queue.h" #undef TYPE 

请注意,除非首先进行typedef否则这将不适用于复合types(例如, unsigned char队列)。

哦,记住,如果这个代码在任何地方都没有被实际使用,那么你甚至不知道它在语法上是否正确。

编辑:还有一件事:你需要手动pipe理代码的实例化。 如果您的“模板”代码不是全部的内联函数,那么您必须进行一些控制,以确保事物只被实例化一次,因此链接器不会吐出一堆“多实例Foo”错误。

要做到这一点,你必须把非内联的东西放在头文件的“实现”部分:

 #ifdef implementation_##TYPE /* Non-inlines, "static members", global definitions, etc. go here. */ #endif 

然后,在每个模板变体的所有代码中,您必须:

 #define TYPE Packet #define implementation_Packet #include "Queue.h" #undef TYPE 

另外,这个实现部分需要超出标准的#ifndef / #define / #endif litany,因为你可能会在另一个头文件中包含模板头文件,但是之后需要在.c文件中实例化。

是的,它变得很难看 这就是为什么大多数C程序员甚至不尝试。

RAII。

特别是在具有多个返回点的函数中,例如不必记住在每个返回点上释放互斥量。

那么,忘记你漂亮的代码,习惯于你所有的返回点(除了函数的结尾) goto s:

 TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { TYPE * result; Mutex_Lock(this->lock); if(this->head == this->tail) { result = 0; goto Queue_##TYPE##_Top_exit:; } /* Figure out `result` for real, then fall through to... */ Queue_##TYPE##_Top_exit: Mutex_Lock(this->lock); return result; } 

一般的破坏者。

即你为MyClass写了一次,然后如果一个MyClass实例是MyOtherClass的一个成员,MyOtherClass不必显式地去初始化MyClass实例 – 它的自动调用。

对象构造必须以相同的方式明确处理。

命名空间。

这实际上是一个简单的解决办法:只在每个符号上加上一个前缀。 这是我之前谈到的源代码膨胀的主要原因(因为类是隐式的命名空间)。 C族人一直以来都活在这个地方,而且可能永远都不会有什么大不了的。

因人而异

我从C ++转移到C的原因不同(某种过敏反应),只有一些我想念的东西和我获得的一些东西。 如果你坚持C99的话,如果你愿意的话,还有一些构造可以让你很好的安全编程

  • 指定的初始化程序(最终与macros组合)使简单类的初始化与构造函数一样简单
  • 复合文字的临时variables
  • for -scopevariables可以帮助您进行范围绑定的资源pipe理 ,尤其是确保unlock互斥锁或free数组,即使在初步的函数返回
  • 可以使用__VA_ARGS__macros来为函数使用默认参数,并执行代码展开
  • inline函数和macros,它们很好地结合在一起,以replace(sorting)重载函数

没有像STL存在C.
有可用的库提供类似的function,但它不是内置的了。

认为这将是我最大的问题之一…知道用哪种工具可以解决问题,但不能使用我必须使用的语言提供的工具。

C和C ++的区别在于代码行为的可预测性。

用C ++来精确地预测你的代码将在C中做什么是一件容易的事情,要想得到一个精确的预测可能会变得有些困难。

C中的可预测性可以让你更好地控制你的代码正在做什么,但这也意味着你必须做更多的事情。

在C ++中,可以编写更less的代码来完成相同的任务,但是(对于我来说)我偶尔也会遇到目标代码在内存中的布局以及预期的行为。

在我的工作中,顺便说一下,我经常在C和C ++之间来回切换。

当我在C中时,我想念C ++:

  • 模板(包括但不限于STL容器)。 我将它们用于特殊计数器,缓冲池等(我build立了自己的类模板和函数模板库,用于不同的embedded式项目)

  • 非常强大的标准库

  • 析构函数,这当然使RAII成为可能(互斥锁,中断禁止,跟踪等)

  • 访问说明符,以更好地执行谁可以使用(不看)什么

我在大项目上使用inheritance,而且C ++内置的支持比embedded基类作为第一个成员的C“hack”要好得多(更不要说自动调用构造函数,init。lists等等)。 )但上面列出的项目是我最想念的。

此外,大概只有三分之一的embedded式C ++项目使用exception,所以我已经习惯了没有它们的生活,所以当我回到C时,我不会错过它们。

另一方面,当我回到一个拥有大量开发人员的C项目时,我习惯向那些离开人们解释的整个类的C ++问题。 主要是因为C ++的复杂性,以及认为自己知道发生了什么的人,但是他们确实处于C ++置信度曲线的“C类”部分。

考虑到select,我更喜欢在一个项目上使用C ++,但是只有当这个团队在这个语言上非常扎实。 当然,假设它不是一个8KμC的项目,我仍然有效地写“C”。

几个观察

  • 除非你打算使用你的C ++编译器来构build你的C(如果你坚持C ++的一个好的定义子集,这是可能的),你很快就会发现你的编译器在C中允许的东西,这在C ++中是一个编译错误。
  • 没有更神秘的模板错误(耶!)
  • 没有(语言支持)面向对象编程

和C ++或者C / C ++而不是纯C混合使用的理由差不多。我可以没有命名空间,但是如果代码标准允许的话,我可以一直使用它们。 原因是你可以在C ++中编写更紧凑的代码。 这对我来说非常有用,我使用C ++编写服务器,而这些服务器往往会不时崩溃。 在这一点上,如果你正在查看的代码很短并且很有帮助,那么它会有很大的帮助。 例如考虑下面的代码:

 uint32_t ScoreList::FindHighScore( uint32_t p_PlayerId) { MutexLock lock(m_Lock); uint32_t highScore = 0; for(int i = 0; i < m_Players.Size(); i++) { Player& player = m_Players[i]; if(player.m_Score > highScore) highScore = player.m_Score; } return highScore; } 

在C中看起来像:

 uint32_t ScoreList_getHighScore( ScoreList* p_ScoreList) { uint32_t highScore = 0; Mutex_Lock(p_ScoreList->m_Lock); for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++) { Player* player = p_ScoreList->m_Players[i]; if(player->m_Score > highScore) highScore = player->m_Score; } Mutex_UnLock(p_ScoreList->m_Lock); return highScore; } 

不是一个世界的差异。 还有一行代码,但这往往会加起来。 正规的你尽量保持清洁和精益,但有时你必须做更复杂的事情。 而在这种情况下,你很重视你的线数。 当你试图弄清楚为什么你的广播networking突然停止传递消息的时候,还有一件事是要看的。

无论如何,我发现C ++允许我以安全的方式做更复杂的事情。

我认为为什么c ++更难被embedded式环境所接受的主要问题是因为缺乏了解如何正确使用c ++的工程师。

是的,同样的推理也可以适用于C,但幸运的是C中没有那么多的陷阱,可以在脚下自己射击。 C ++另一方面,你需要知道什么时候不要在c ++中使用某些特性。

总而言之,我喜欢c ++。 我在O / S服务层,驱动程序,pipe理代码等等上使用它。但是如果你的团队没有足够的经验,这将是一个艰难的挑战。

我有两个经验。 当其他人没有做好准备的时候,这真是一场灾难。 另一方面,这是很好的经验。