从C ++迁移到C

时间:2010-10-30 11:35:17

标签: c++ c

用C ++编写了几年后,我最近在嵌入式领域的C中提供了一个编码工作。

暂不讨论在嵌入式字段中解雇C ++是对还是错的问题,C ++中有一些特性/习惯用法我会错过很多。仅举几例:

  • 通用,类型安全的数据结构(使用模板)。
  • RAII。特别是在具有多个返回点的函数中,例如不必记住在每个返回点上释放互斥锁。
  • 一般的析构函数。即你为MyClass写一次d'tor,然后如果一个MyClass实例是MyOtherClass的成员,MyOtherClass不必显式取消初始化MyClass实例 - 它的自动调用它。
  • 命名空间。

从C ++到C的经历是什么? 您找到的C替代品是您最喜欢的C ++特性/习语吗?您是否发现了C ++的C功能?

9 个答案:

答案 0 :(得分:64)

在嵌入式项目上工作时,我尝试过在所有C中工作过一次,但是无法忍受。它只是如此冗长以至于难以阅读任何内容。此外,我喜欢我编写的优化嵌入式容器,这些容器必须变得更安全,更难以修复#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-char行长度限制)。两者都会产生相同的代码,因此它不像目标处理器那样关心!

有一次(早在1995年),我尝试为多处理器数据处理程序编写了大量的C语言。每个处理器都有自己的内存和程序的类型。供应商提供的编译器是一个C编译器(某种类型的HighC衍生版本),它们的库是封闭的源代码,所以我不能使用GCC来构建,并且他们的API设计的思路是你的程序主要是初始化/进程/终止多样性,因此处理器间通信充其量是最基本的。

我放弃了大约一个月之前,发现了cfront的副本,并将其入侵到makefile中,因此我可以使用C ++。 Cfront甚至不支持模板,但C ++代码更加清晰。

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

C与模板最接近的是声明一个包含大量代码的头文件:

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

然后用以下内容将其拉入:

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

请注意,这不适用于复合类型(例如,unsigned char没有队列),除非您先创建typedef

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

编辑:还有一件事:您需要手动管理代码的实例化。如果你的“模板”代码不是所有内联函数,那么你必须进行一些控制以确保事情只被实例化一次,这样你的链接器就不会吐出一堆“多个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。

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

好吧,忘记漂亮的代码并习惯所有的返回点(除了函数的结尾)being gotos:

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写一次d'tor,然后如果一个MyClass实例是MyOtherClass的一个成员,MyOtherClass就不必显式地取消初始化MyClass实例 - 它的自动调用它。

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

命名空间。

这实际上是一个简单的解决方法:只需在每个符号上添加一个前缀。这是我之前谈到的源膨胀的主要原因(因为类是隐式命名空间)。 C人们一直生活在这里,永远,并且可能不会看到重要的事情。

YMMV

答案 1 :(得分:16)

由于不同的原因(某种过敏反应,我)从C ++转移到C语言,并且我只有一些我想念的东西以及我获得的一些东西。如果您坚持使用C99,如果可以的话,有一些结构可以让您编程非常安全,特别是

  • 指定的初始化程序(最终 结合宏)make 初始化简单类为 无痛的建设者
  • 临时变量的复合文字
  • for - 范围变量可以帮助您执行scope bound resource management,特别是确保unlock个互斥锁或free数组,即使在初步函数返回时也是如此
  • __VA_ARGS__宏可用于为函数提供默认参数并执行代码展开
  • inline功能和宏结合得很好,可以替换(排序)重载函数

答案 2 :(得分:8)

没有类似于ST的STL 有可用的库提供类似的功能,但它不再构建。

认为这将是我最大的问题之一......了解哪种工具我可以解决问题,但没有使用我必须使用的语言提供的工具。

答案 3 :(得分:8)

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

更准确地预测你的代码在C中会做什么,在C ++中,提出准确的预测可能会变得有点困难。

C中的可预测性使您可以更好地控制代码的运行,但这也意味着您必须做更多的事情。

在C ++中,你可以编写更少的代码来完成同样的事情,但是(对我而言)我偶尔会知道目标代码是如何在内存中布局的,而且是预期的行为。

答案 4 :(得分:7)

在我的工作中 - 顺便说一句,这是我的工作 - 我不断地转回&在C和C ++之间。

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

  • 模板(包括但不限于STL容器)。我将它们用于特殊计数器,缓冲池等等(构建我自己的类模板库和我在不同嵌入式项目中使用的函数模板)

  • 非常强大的标准库

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

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

我在较大的项目中使用继承,而C ++对它的内置支持更加清晰&比嵌入基类作为第一个成员的C“hack”更好(更不用说构造函数的自动调用,init。列表等),但上面列出的项目是我最想念的项目。

另外,我工作的嵌入式C ++项目可能只有大约三分之一使用异常,所以我已经习惯了没有它们的生活,所以当我回到C时,我不会错过太多。

另一方面,当我回到一个拥有大量开发人员的C项目时,我会习惯向那些消失的人解释C ++问题。主要是由于C ++的复杂性引起的问题,以及那些认为自己知道发生了什么的人,但是他们真的处于C++ confidence curve的“C with classes”部分。

考虑到这个选择,我更喜欢在项目中使用C ++,但前提是团队在语言上非常扎实。当然,假设它不是一个8KμC项目,无论如何我都在写“C”。

答案 5 :(得分:3)

观察结果

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

答案 6 :(得分:2)

与使用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; 
}

不是一个与众不同的世界。还有一行代码,但这往往会增加。正常地,你尽力保持清洁和精益,但有时你必须做一些更复杂的事情。在这些情况下,您重视行数。当你试图找出你的广播网络突然停止传递消息的原因时,还有一条线要看。

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

答案 7 :(得分:0)

我认为c ++在嵌入式环境中难以被接受的主要问题是因为缺乏了解如何正确使用c ++的工程师。

是的,同样的推理也适用于C,但幸运的是,C中没有那么多陷阱可以让你自己陷入困境。另一方面,C ++需要知道何时不使用c ++中的某些特性。

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

我对两者都有过经验。当团队的其他成员没有做好准备时,这是一场灾难。另一方面,这是很好的经历。

答案 8 :(得分:0)

是的!我已经经历过这两种语言,而我发现C ++是更友好的语言。它具有更多功能。最好说C ++是C语言的超集,因为它提供了其他特性,例如多态性,中介性,运算符和函数重载,用户定义的数据类型,而C语言并未真正支持。面向对象编程的帮助,这是从C转向C ++的主要原因。