从C++迁移到C

geo*_*rge 82 c c++

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

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

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

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

Mik*_*one 65

在一个嵌入式项目上工作,我尝试过在所有C中工作一次,但是无法忍受.它只是如此冗长以至于难以阅读任何内容.此外,我喜欢我编写的优化嵌入式容器,这些容器必须变得更安全,更难以修复#define块.

C++中的代码看起来像:

if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
    pktQueue.Dequeue(1);
Run Code Online (Sandbox Code Playgroud)

变成:

if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
    Queue_Packet_Dequeue(pktQueue, 1);
Run Code Online (Sandbox Code Playgroud)

许多人可能会说这很好,但如果你不得不做一对"方法"调用,那就太荒谬了.两行C++将变为C中的五行(由于80-char行长度限制).两者都会生成相同的代码,因此它不像目标处理器那样关注!

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

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

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

C对模板最接近的是声明一个包含大量代码的头文件,如下所示:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }
Run Code Online (Sandbox Code Playgroud)

然后把它拉进去:

#define TYPE Packet
#include "Queue.h"
#undef TYPE
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:还有一件事:你需要手动管理代码的实例化.如果你的"模板"代码不是所有的内联函数,那么你必须加入一些控制来确保事物只被实例化一次,这样你的链接器就不会吐出一堆"Foo的多个实例"错误.

为此,您必须将非内联内容放在头文件的"实现"部分中:

#ifdef implementation_##TYPE

/* Non-inlines, "static members", global definitions, etc. go here. */

#endif
Run Code Online (Sandbox Code Playgroud)

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

#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE
Run Code Online (Sandbox Code Playgroud)

此外,这实现部分需要被外界标准#ifndef/ #define/ #endif一长串,因为你可能包括另一头文件模板的头文件,但需要在以后实例.c文件.

是的,它快速变得难看.这就是大多数C程序员甚至都不尝试的原因.

RAII.

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

好吧,忘记漂亮的代码并习惯你的所有返回点(除了函数的结尾)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;
}
Run Code Online (Sandbox Code Playgroud)

一般的析构函数.

也就是说,你为MyClass写了一次,如果一个MyClass实例是MyOtherClass的成员,MyOtherClass就不必显式地取消初始化MyClass实例 - 它的自动调用它.

必须以相同的方式显式地处理对象构造.

命名空间.

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

因人而异

  • 如果你试图强迫它成为C++,你当然讨厌C.我怀疑如果你试图将$ more_expressive_language中的功能强加到它中,C++看起来会很棒.不是对你的帖子的批评,只是观察:-) (57认同)
  • 同意@Mads.没有理由为你不需要的功能完成所有这些.我发现我喜欢类似于GTK库的风格.将"类"定义为结构,然后创建一致的方法,如my_class_new(),然后将其传递给"方法":my_class_do_this(my_class_instance) (5认同)
  • @george:我不想这么说,但是我看到的大多数嵌入式C代码都是C标准的.例如,我现在正在使用Atmel的at91lib,它要求你编写一个"board.h"文件,其中大部分代码作为依赖项引入.(对于他们的演示板,这个标题长792行.)另外,你必须为你的电路板定制的"LowLevelInit()"函数几乎完全是寄存器访问,像`AT91C_BASE_PMC-> PMC_MOR =(0x37 << 16)| BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;` (2认同)

Jen*_*edt 16

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

  • 指定的初始化器(最终与宏结合)使得简单类的初始化与构造函数一样轻松
  • 临时变量的复合文字
  • for-scope变量可以帮助您进行范围绑定资源管理,特别是确保unlock互斥锁或free数组,即使在初步函数返回时也是如此
  • __VA_ARGS__ 宏可用于为函数提供默认参数并执行代码展开
  • inline 结合良好的函数和宏来替换(类型)重载函数

  • @Mike:特别针对哪一部分?如果您按照我给'for`范围给出的链接,您将登陆P99,在那里您也可以查看其他部分的示例和说明. (2认同)

MOn*_*DaR 8

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

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


小智 8

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

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

C中的可预测性使您可以更好地控制代码的工作,但这也意味着您必须执行更多操作.

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

  • 每当我担心代码真正在做什么时,我都会将`-s`标志添加到`gcc`以获取程序集转储,搜索关注的函数并开始阅读.这是学习任何编译语言怪癖的好方法. (4认同)
  • 这也是浪费时间,因为C++生成的程序集就像阅读P​​erl一样.Bravo无论如何都要看. (2认同)

Dan*_*Dan 7

在我的工作中 - 顺便说一下,我是嵌入式的 - 我经常在C和C++之间来回切换.

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

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

  • 非常强大的标准库

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

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

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

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

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

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

  • "C++ Confidence Curve"让我感到困扰.它的编写方式和注释意味着C++是绝望的,失败的原因,或者其他什么.我错过了什么吗? (2认同)