1. ホーム
  2. c++

[解決済み] C++からCへの移行

2023-07-03 12:27:14

質問

C++で数年間コーディングした後、最近、組み込み分野でCでコーディングする仕事を依頼されました。

組み込み分野で C++ を否定することが正しいか間違っているかという問題はさておき、C++ には私がとても恋しくなるような機能/慣用句がいくつか存在します。ほんの少しですが、例を挙げてみましょう。

  • 一般的な、型安全なデータ構造 (テンプレートを使用)。
  • RAII。特に複数のリターンポイントを持つ関数において、例えば各リターンポイントでmutexを解放することを覚える必要がない。
  • 一般にデストラクタ。例えば、MyClass のために一度 d'tor を書くと、MyClass インスタンスが MyOtherClass のメンバーである場合、MyOtherClass は明示的に MyClass インスタンスを初期化する必要がありません - その d'tor は自動的に呼び出されます。
  • 名前空間。

C++からCに移行した経験を教えてください。

お気に入りの C++ の機能/慣用句のために、どのような C の代用品が見つかりましたか?また、C++ にあればいいと思う C の機能を発見しましたか?

どのように解決しましたか?

組み込みプロジェクトに取り組んでいて、一度オール 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);

は、おそらく多くの人が問題ないと言うでしょうが、1行で2つ以上の "メソッド" を呼び出す必要がある場合は、ばかばかしいことになります。 C++の2行がCの5行になってしまいます(80文字の行長制限のため)。 どちらも同じコードを生成するので、ターゲット プロセッサが気にするようなことではありません!

あるとき (1995 年頃)、私はマルチプロセッサのデータ処理プログラム用に多くの C 言語を書こうとしました。 各プロセッサが独自のメモリとプログラムを持っているようなものです。 ベンダーが提供するコンパイラーは C コンパイラー (ある種の HighC 派生) で、ライブラリーはクローズド ソースだったので、GCC を使用してビルドすることができませんでした。

あきらめる前に 1 か月ほどやってみたのですが、その間に cfront を見つけ、C++ を使えるように makefile をハックしました。 Cfront はテンプレートさえサポートしていませんでしたが、C++ のコードはずっとずっと明快でした。

一般的で型安全なデータ構造 (テンプレートを使用)。

C言語でテンプレートに最も近いものは、多くのコードでヘッダーファイルを宣言することで、次のようになります。

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

のようなもので引っ張ります。

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

これは複合型では動作しないことに注意してください。 unsigned char を作らない限り)。 typedef を最初に作らない限り

ああ、このコードが実際にどこにも使われていなければ、構文的に正しいかどうかさえもわからないということを忘れないでください。

EDITです。 もう一つ、あなたは 手動 でコードのインスタンス化を管理する必要があります。 もしあなたの "テンプレート" コードが すべて インライン関数でない場合、リンカーが "multiple instances of 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