C ++からCへの移行


83

C ++でのコーディングを数年行った後、私は最近、組み込み分野でCでのコーディングの仕事を提供されました。

埋め込みフィールドでC ++を却下することが正しいか間違っているかという問題はさておき、C ++にはいくつかの機能/イディオムがあります。ほんの数例を挙げると:

  • 一般的なタイプセーフなデータ構造(テンプレートを使用)。
  • RAII。特に、複数のリターンポイントを持つ関数では、たとえば、各リターンポイントでミューテックスを解放することを覚えておく必要はありません。
  • 一般的なデストラクタ。つまり、MyClassに対してd'torを1回作成すると、MyClassインスタンスがMyOtherClassのメンバーである場合、MyOtherClassはMyClassインスタンスを明示的に非初期化する必要はありません。そのd'torは自動的に呼び出されます。
  • 名前空間。

C ++からCに移行した経験は何ですか?
お気に入りのC ++機能/イディオムの代わりにどのCを見つけましたか?C ++に必要なC機能を見つけましたか?


12
アドバイスではなく経験を求めているだけなら、おそらくコミュニティwikiであるはずです。
ピーターアレクサンダー

6
あなたはProg.SEに興味があるかもしれません。

11
@Peter:OPが質問をCWにすることはできなくなり、それがまだ可能だったときよりも多くの担当者が必要になりました。より多くのユーザーが「コミュニティ所有」の投稿を編集できるようにする以外の理由で質問をコミュニティウィキにする必要があると思われる場合、本当に必要なのは質問を閉じることです。

4
この質問はprogrammers.seに適しているのではないでしょうか。それは間違いなく「本当の」質問なので、私たちはそれを再開し、代わりにそれを移動することに投票すると言います。そして、それは不可能です。OK。
Lasse V. Karlsen 2010年

21
移動は、prog SEのベータ版が終了するまで発生しません。いずれにせよ、このQAへのアプローチは頭を悩ませていると思います。それはコミュニティを断片化し、ユーザーを苛立たせ、質問と回答を複製します。これは、以前は単一の「プログラマー」サイトでアクセスおよびナビゲートできた、組織化されていない情報の騒乱を生み出しています。さらに、このような質問は、巨大な見解と信じられないほどの賛成票を持っており、5つのクローザーとコミュニティ全体の間で私を怒らせます。
ステファノボリーニ2010年

回答:


68

組み込みプロジェクトに取り組んでいて、一度すべての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つ以上の「メソッド」呼び出しをしなければならない場合はばかげています。2行のC ++は5行のCになります(80文字の行の長さの制限のため)。どちらも同じコードを生成するため、ターゲットプロセッサが気にするようなものではありません。

ある時(1995年にさかのぼります)、私はマルチプロセッサデータ処理プログラムのためにたくさんのCを書いてみました。各プロセッサが独自のメモリとプログラムを持っている種類。ベンダー提供のコンパイラーはCコンパイラー(ある種のHighC派生物)であり、ライブラリーはクローズドソースであるため、GCCを使用してビルドすることはできませんでした。また、APIは、プログラムが主に初期化/プロセスになるという考え方で設計されました。 /種類を終了するので、プロセッサ間通信はせいぜい初歩的なものでした。

私はあきらめる前に約1か月かかり、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

ああ、覚えておいてください。このコードが実際にどこでも使用されていない場合は、構文的に正しいかどうかさえわかりません。

編集:もう1つ、コードのインスタンス化を手動で管理する必要があります。「テンプレート」コードがすべてインライン関数ではない場合は、リンカーが「Fooの複数のインスタンス」エラーの山を吐き出さないように、物事が1回だけインスタンス化されるように制御する必要があります。 。

これを行うには、インライン化されていないものをヘッダーファイルの「実装」セクションに配置する必要があります。

#ifdef implementation_##TYPE

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

#endif

そして、テンプレートバリアントごとのすべてのコードの1つの場所で、次のことを行う必要があります。

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

また、この実装セクションのニーズがあることを外に標準#ifndef/ #define/#endifあなたは別のヘッダファイル内のテンプレートのヘッダファイルを含むこともできるので、嫌になるほど、しかし、でその後インスタンス化する必要があります.cファイル。

うん、それは醜い速くなります。そのため、ほとんどのCプログラマーは試していないのです。

RAII。

特に、複数のリターンポイントを持つ関数では、たとえば、各リターンポイントでミューテックスを解放することを覚えておく必要はありません。

さて、あなたのきれいなコードを忘れて、すべてのリターンポイント(関数の終わりを除く)sであることに慣れてくださいgoto

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を1回作成すると、MyClassインスタンスがMyOtherClassのメンバーである場合、MyOtherClassはMyClassインスタンスを明示的に非初期化する必要はありません。そのd'torは自動的に呼び出されます。

オブジェクトの構築は、同じ方法で明示的に処理する必要があります。

名前空間。

これは実際には簡単に修正できます。すべてのシンボルにプレフィックスを付けるだけです。これは、前に説明したソースの肥大化の主な原因です(クラスは暗黙の名前空間であるため)。Cの人々はこれを永遠に生きてきており、おそらく大したことは何なのかわからないでしょう。

YMMV


59
もちろん、Cを強制的にC ++にしようとすると、Cは嫌いです。$ more_expressive_languageの機能をC ++に適用しようとすると、C ++が見栄えがするのではないかと思います。あなたの投稿に対する批評ではなく、ただの観察です:-)

RAIIの代わりにgotoテクニックについて:それはメンテナンスの悪夢ではありませんか?つまり、クリーンアップが必要なコードパスを追加したり、関数内の順序を変更したりする場合は、最後にあるgotoラベルに移動してそれらも変更することを忘れないでください。クリーンアップが必要なもののすぐ隣にクリーンアップコードを登録する手法が欲しいのですが。
ジョージ2010年

2
@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;
Mike DeSimone 2010年

1
ああ、そしてそこには何もあなたにそれを教えてくれませんBOARD_OSCOUNT(これは時計が切り替わるのを待つためのタイムアウトの値です;クリア、ハァッ?)は実際には#defineinboard.hです。また、同じ関数には、コピーアンドペーストされたスピンループコードがたくさんあり、2行に変換されているはずです#define(そして、それを実行すると、数バイトのコードが節約され、レジスタセットとスピンループをより明確にすることで、関数が読みやすくなります)。Cを使用する大きな理由の1つは、すべてを細かく管理して最適化できることですが、私が見たほとんどのコードは気になりません。
Mike DeSimone 2010年

5
@Madsに同意します。本当に必要のない機能について、これらすべてを実行する理由はありません。GTKライブラリに似たスタイルを楽しんでいます。「クラス」を構造体として定義し、my_class_new()などの一貫したメソッドを作成して、それを「メソッド」に渡​​します。my_class_do_this(my_class_instance)
Max

17

私は別の理由でC ++からCに移行しました(ある種のアレルギー反応;)。私が見逃していることと得たことがいくつかあります。C99に固執する場合は、可能であれば、特に非常にうまく安全にプログラミングできる構造があります。

  • 指定された初期化子(最終的にはマクロと組み合わされる)により、単純なクラスの初期化がコンストラクターと同じくらい簡単になります
  • 一時変数の複合リテラル
  • for-スコープ変数は、スコープにバインドされたリソース管理を行うのに役立つ場合があります。特に、予備的な関数の戻り値の下でも、unlockミューテックスまたはfree配列を確実に処理するために役立ちます。
  • __VA_ARGS__ マクロを使用して、関数へのデフォルトの引数を設定し、コードの展開を行うことができます
  • inline オーバーロードされた関数(の一種)を置き換えるためにうまく組み合わされた関数とマクロ

2
@マイク:特にどの部分ですか?私がforスコープに与えたリンクをたどると、P99にたどり着きます。そこでは、他の部分の例や説明も探すことができます。
Jens Gustedt 2010年

1
@マイク:どうぞ
george 2010年

@george:ありがとう!@イェンス:他の4つの例。私は自分のCに遅れ​​をとっています。最後に、ランタイムサイズの自動割り当て(スタック)配列(例void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... })とフィールド名による構造の初期化(例)が追加されたと聞きましstruct Foo bar = { .field1 = 5, .field2 = 10 };た。後者は、特に非PODオブジェクト(例UART uart[2] = { UART(0x378), UART(0x278) };)でC ++で見たいものです。。
Mike DeSimone 2010年

@Mike:はい、可変長配列(VLA)がありますが、スタックオーバーフローが発生する可能性があるため、使用するのは少し危険かもしれません。あなたが説明する2番目はまさに「指定された初期化子」なので、そこにあなた自身の例があります;-)他の人のために、あなたが「関連ページ」をクリックすると上のP99リンクで情報を見つけます。
Jens Gustedt 2010年

8

CにはSTLのような
ものはありません。同様の機能を提供するライブラリが利用可能ですが、それはもう組み込まれていません。

それが私の最大の問題の1つだと思います...どのツールで問題を解決できるかはわかっていますが、使用する言語で利用できるツールがありません。


これは本当です。Cにどのコンテナクラスライブラリを使用すべきかについて誰かが詳しく説明できますか?それとも「自分で書く」という答えですか?
Sandeep 2010年

@Sandeep:初心者にとって、この答えは、標準のライブラリにないコンテナーについてのみ正しいです。STLに相当するもの(C ++の最良の部分)がないことを除けば、C標準ライブラリははるかに優れています。POSIXには、libcにあるqsortに加えて、tsearch、lsearch、hsearch、およびbsearchが含まれています。GlibはCの決定的な「ブースト」です。グッズ(コンテナを含む)が満載されているのを見てください。library.gnome.org/devel/glib/stable。Glibは、BoostとQtを超える組み合わせであるGtk +とも統合されます。SubversionやApacheなどのxplatform関連で人気のあるlibaprもあります。
マットジョイナー2010年

stlと競合する可能性のあるcのライブラリが見つかりません。使用が難しく、保守が難しく、cライブラリをstlのように汎用的に保ちたい場合、パフォーマンスはstlのライバルではありません。 cの制限、およびcライブラリにstlのようなものを含めることができない理由。これは、cにはstlのようなものを開発する機能がないためです。
StereoMatching 2013

8

CとC ++の違いは、コードの動作の予測可能性です。

コードがCで何をするかを非常に正確に予測する方が簡単ですが、C ++では、正確な予測を行うのが少し難しくなる可能性があります。

Cの予測可能性により、コードの実行内容をより適切に制御できますが、それはまた、より多くのことを実行する必要があることを意味します。

C ++では、同じことを行うために書くコードを減らすことができますが、(少なくとも私にとっては)オブジェクトコードがメモリにどのように配置されているか、そしてそれが期待される動作であるかを時々知るのに苦労します。


4
コードが実際に何をしているのか心配するときはいつでも、-sフラグを追加しgccてアセンブリダンプを取得し、問題の関数を検索して、読み取りを開始します。これは、コンパイルされた言語の癖を学ぶのに最適な方法です。
Mike DeSimone 2010年

2
また、C ++で生成されたアセンブリはPerlを読むようなものであるため、時間の無駄です。とにかく見てくれてブラボー。
マットジョイナー2010年

7

ちなみに、埋め込まれている私の仕事では、CとC ++を絶えず切り替えています。

私がCにいるとき、私はC ++を見逃しています。

  • テンプレート(STLコンテナを含むがこれに限定されない)。特別なカウンターやバッファープールなどに使用します(さまざまな組み込みプロジェクトで使用するクラステンプレートと関数テンプレートの独自のライブラリを構築しました)

  • 非常に強力な標準ライブラリ

  • もちろんRAIIを可能にするデストラクタ(ミューテックス、割り込み無効化、トレースなど)

  • 誰が何を使用できるか(見えないか)をより適切に強制するためのアクセス指定子

私は大規模なプロジェクトで継承を使用していますが、C ++の組み込みサポートは、基本クラスを最初のメンバーとして埋め込むCの「ハック」よりもはるかにクリーンで優れています(コンストラクター、初期化リストなどの自動呼び出しは言うまでもありません)。 )しかし、上記の項目は私が最も見逃しているものです。

また、私が取り組んでいる組み込みC ++プロジェクトの約3分の1だけが例外を使用しているので、例外なしで生活することに慣れているので、Cに戻ったときにそれらを見逃しすぎません。

反対に、かなりの数の開発者がいるCプロジェクトに戻ると、去っていく人々に説明するのに慣れているC ++の問題のクラス全体があります。主にC ++の複雑さによる問題であり、何が起こっているのかを知っていると思う人もいますが、実際にはC ++の信頼曲線の「Cwithclasses」の部分にいます。

選択肢があれば、プロジェクトでC ++を使用することをお勧めしますが、それはチームが言語にかなり固執している場合に限られます。もちろん、とにかく効果的に「C」を書いている8KμCプロジェクトではないと仮定します。


2
その「C ++信頼曲線」は少し気になります。それが書かれている方法とコメントは、C ++が絶望的である、失われた原因、または何でもであることを意味します。私は何かが足りないのですか?
Mike DeSimone 2010年

思い切って、数年後にお会いしましょう。ほとんどの優れたプログラマーは、酸っぱい味で反対側に出てきます。
マットジョイナー2010年

3

いくつかの観察

  • C ++コンパイラを使用してCをビルドする予定がない限り(C ++の明確に定義されたサブセットに固執する場合は可能です)、コンパイラがCで許可することは、C ++でのコンパイルエラーになります。
  • 不可解なテンプレートエラーはもうありません(イェーイ!)
  • (言語でサポートされている)オブジェクト指向プログラミングはありません

Cはテンプレートをサポートしていませんが、「一般的なパラダイム」が必要ないという意味ではありません。Cでは、「一般的なパラダイム」が必要な場合は、void *とマクロを使用してテンプレートを模倣する必要があります。void*はタイプセーフではありません。マクロのエラーまた、かなりくだらない、テンプレートよりも優れていません。テンプレートは、マクロよりもはるかに読みやすく、保守が簡単で、タイプセーフです。
StereoMatching 2013

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; 
}

違いの世界ではありません。もう1行のコードですが、それは合計される傾向があります。通常、あなたはそれを清潔で無駄のない状態に保つために最善を尽くしますが、時にはもっと複雑なことをしなければならないこともあります。そして、そのような状況では、行数を評価します。ブロードキャストネットワークが突然メッセージの配信を停止する理由を理解しようとするときに、もう1行を確認する必要があります。

とにかく、C ++を使用すると、より複雑なことを安全に行うことができます。


。Cでは、あなたは、この「について(int型iは0 =」を行うことはできません
ビクター

6
ビクターそれは有効なc99です(またはいくつかの入力の問題のためにc ++コンパイラでcをコンパイルします)。
Roman A. Taycher 2010年

私は安全を信頼できないと思います。したがって、ミューテックスはスコープされます。これで、例外が「さまよった」場合にロックが解除される理由がわかりません。いつロックが解除されるかさえわかりません。コードのどの部分でも、十分な数があると判断してスローする可能性があります。これらの余分な暗黙の「安全性」は、バグを隠蔽している可能性があります。
マットジョイナー2010年

マット、ロックが解除された理由はわかっています。通常、プログラムがスコープの最後に達すると、ミューテックスがロック解除されます。手作りのコードでロックを解除する必要はありません。メンテナンスの悪夢です。例外が発生すると、ミューテックスのロックが解除され、例外をキャッチしてエラーメッセージを読み取ることができます。エラーメッセージが十分であるかどうかは、例外をどのように処理するかによって異なります。
StereoMatching 2013

0

組み込み環境でc ++が受け入れられにくい主な問題は、c ++の適切な使用方法を理解しているエンジニアが不足しているためだと思います。

はい、同じ理由がCにも当てはまりますが、幸いなことに、Cには足を撃つ可能性のある落とし穴はそれほど多くありません。一方、C ++では、C ++で特定の機能を使用しない場合を知っておく必要があります。

全体として、私はc ++が好きです。私はそれをO / Sサービス層、ドライバー、管理コードなどで使用しています。しかし、チームがそれについて十分な経験を持っていない場合、それは難しい課題になるでしょう。

私は両方の経験がありました。チームの他のメンバーがその準備ができていなかったとき、それは完全な惨事でした。一方で、良い経験でした。


0

はい!私はこれらの言語の両方を経験しましたが、C ++の方がよりフレンドリーな言語であることがわかりました。それはより多くの機能を容易にします。C ++は、ポリモーフィズム、インターティタンス、演算子と関数のオーバーロード、Cでは実際にはサポートされていないユーザー定義データ型などの追加機能を提供するため、C言語のスーパーセットであると言ったほうがよいでしょう。 CからC ++に移行する主な理由であるオブジェクト指向プログラミングの助け。


C ++は実際にはCのスーパーセットではありません。C ++コンパイラでのコンパイルに失敗するCコードを書くのは非常に簡単です。一方、Objective-Cのである(?)であったC.の完全なスーパーセット
無から

@exnihiloここでの規則のスーパーセットは、C ++にさらに多くの機能が付属することを定義することです。また、構文とセマンティクスを改善し、エラーの可能性を減らします。const int aのように、C ++ではコンパイルされなかったが、Cではコンパイルできるコードがいくつかあります。これにより、C ++ではエラーが発生します。これは、Cでは概念がコンパイルされる間、宣言時に定数を初期化する必要があるためです。したがって、スーパーセットは数学(A⊂B)のように厳密で高速なルールではなく、C ++がオブジェクト指向の概念などの追加機能を提供するという近似です。
kaynat liaqat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.