从 C 移动到 C

新手上路,请多包涵

在用 C++ 编码几年后,我最近获得了一份在嵌入式领域用 C 编码的工作。

撇开在嵌入式领域摒弃 C++ 是对还是错的问题,C++ 中的一些特性/习语我会错过很多。仅举几个:

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

你从 C++ 迁移到 C 的经历是什么?

您为您最喜欢的 C++ 特性/习语找到了哪些 C 替代品?你有没有发现任何你希望 C++ 拥有的 C 特性?

原文由 george 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 459
2 个回答

在一个嵌入式项目上工作,我曾经尝试过使用所有 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 字符行长度限制)。两者都会生成相同的代码,所以它不像目标处理器关心!

有一次(早在 1995 年),我尝试为多处理器数据处理程序编写大量 C 语言。每个处理器都有自己的内存和程序的那种。供应商提供的编译器是 C 编译器(某种 HighC 衍生产品),它们的库是封闭源代码,因此我无法使用 GCC 来构建,并且它们的 API 的设计理念是您的程序主要是初始化/进程/terminate 种类繁多,因此处理器间的通信充其量只是初级的。

在我放弃之前大约一个月,我找到了 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 一连串,因为你可以在另一个头文件中包含模板头文件,之后需要在 .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 编写一次 d’tor,然后如果 MyClass 实例是 MyOtherClass 的成员,则 MyOtherClass 不必显式取消初始化 MyClass 实例 - 它的 d’tor 会自动调用。

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

命名空间。

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

YMMV

原文由 Mike DeSimone 发布,翻译遵循 CC BY-SA 3.0 许可协议

当然,逃避复杂/混乱语法的愿望是可以理解的。有时 C 似乎是解决方案。然而,C++ 是行业支持的地方,包括工具和库,因此很难解决。

C++ 今天有很多特性,包括 lambdas。

一个好的方法是利用 C++ 本身来简化您的代码。对象有利于在底层隔离事物,因此在更高级别上,代码更简单。核心指南推荐具体(简单)对象,因此该方法可以提供帮助。

复杂程度在工程师的控制之下。如果多重继承 (MI) 在某个场景中很有用并且人们更喜欢该选项,那么可以使用 MI。

或者,可以定义接口,从接口继承,并包含实现对象(组合/聚合)并使用内联包装器通过接口公开对象。内联包装器编译为无,即编译为对内部(包含)对象的简单使用,但容器对象似乎具有该功能,就好像使用了多重继承一样。

C++ 也有命名空间,因此即使以类似 C 的风格进行编码,也应该利用命名空间。

人们可以使用语言本身来创建更简单的模式,并且 STL 中充满了示例:数组、向量、映射、队列、字符串、unique_ptr ……并且可以控制(在合理范围内)它们的代码的复杂程度。

所以,回到 C 不是办法,也没有必要。可以以类似 C 的方式使用 C++,或者使用 C++ 多重继承,或者使用介于两者之间的任何选项。

原文由 Coder 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏