web-dev-qa-db-ja.com

関数のオーバーロードのためだけにC ++コンパイラを使用することは悪い習慣ですか?

そのため、特定のプロセッサでCを使用するソフトウェア設計に取り組んでいます。ツールキットには、CおよびC++をコンパイルする機能が含まれています。私がやっていることについては、この環境では動的メモリ割り当ては利用できず、プログラムは全体的にかなり単純です。言うまでもなく、このデバイスにはプロセッサ能力やリソースがほとんどありません。 C++を使用する必要はまったくありません。

とはいえ、関数のオーバーロード(C++の機能)を実行する場所がいくつかあります。いくつかの異なるタイプのデータを送信する必要があり、printfスタイルの書式設定をある種の%s(またはその他の)引数で使用したくない。 printfを実行するC++コンパイラーにアクセスできない一部の人々を見てきましたが、私の場合はC++サポートが利用可能です。

そもそもなぜ関数をオーバーロードする必要があるのか​​という疑問を抱くかもしれません。だから今すぐ答えようと思います。シリアルポートからさまざまな種類のデータを送信する必要があるため、次のデータ型を送信するいくつかのオーバーロードがあります。

unsigned char*
const char*
unsigned char
const char

これらすべてを処理する1つのメソッドを使用しない方がいいと思います。関数を呼び出すとき、シリアルポートに送信するだけでよいので、リソースが足りないので、ほとんどやりたくありません[〜#〜] anything [〜#〜]しかし、私の伝達。

他の誰かが私のプログラムを見て、「なぜCPPファイルを使用しているのですか?」それが私の唯一の理由です。それは悪い習慣ですか?

更新

尋ねられたいくつかの質問に答えたい:

あなたのジレンマに対する客観的な答えは次の要素に依存します:

  1. C++を使用している場合に、実行可能ファイルのサイズが大幅に増加するかどうか。

現在、実行可能ファイルのサイズは、プログラムメモリの4.0%(5248バイトのうち)およびデータメモリの8.3%(342バイトのうち)を消費しています。つまり、C++向けにコンパイルしています... Cコンパイラを使用していないため、Cでどのように見えるかわかりません。私はこのプログラムがこれ以上成長しないことを知っているので、リソースがどれほど限られているかについては、大丈夫だと思います...

  1. C++を使用する場合、パフォーマンスに顕著な悪影響があるかどうか。

まあ、あったとしても何も気づかなかったのですが…。それでも、よくわからないので、この質問をしているのかもしれません。

  1. Cコンパイラのみが利用可能な別のプラットフォームでコードを再利用するかどうか。

これに対する答えは間違いなくnoであることを知っています。実際には別のプロセッサーへの移行を検討していますが、より強力なARMベースのプロセッサー(事実、C++コンパイラーツールチェーンを備えていることはすべて知っています)のみです。

61
Snoop

私はそれを「悪い習慣」と呼ぶほどには行きませんそれ自体ですが、私はそれが本当にあなたの問題に対する正しい解決策であるとは確信していません。必要なのが4つのデータ型を実行する4つの別々の関数だけである場合は、太古の昔からCプログラマが行っていたことができないのはなぜですか。

void transmit_uchar_buffer(unsigned char *buffer);
void transmit_char_buffer(char *buffer);
void transmit_uchar(unsigned char c);
void transmit_char(char c);

とにかく、これはC++コンパイラが舞台裏で行っていることであり、プログラマにとってはそれほど大きなオーバーヘッドではありません。 「なぜC++コンパイラでnot-quite-Cを書いているのか」というすべての問題を回避し、プロジェクトの他の誰もC++のどのビットが「許可」されていないのかで混乱しないことを意味します。

77
Philip Kendall

C++の一部の機能のみを使用して、それ以外の場合はCとして扱うことは、まったく一般的ではありませんが、どちらも前代未聞ではありません。実際、一部の人々は、より厳密で強力な型チェックを除いて、すべてのC++でno機能さえ使用します。彼らは単にCを書いて(C++とCの共通部分のみを書くように注意して)、型チェックのためにC++コンパイラーでコンパイルし、コード生成のためにCコンパイラーでコンパイルします(またはC++コンパイラーをそのまま使用します)。

Linuxは、classのような識別子の名前をklassまたはkclassに変更して、C++コンパイラでLinuxをコンパイルできるように人々が定期的に要求するコードベースの例です。明らかに C++に対するLinusの見解常に撃たれる :-D GCCは、最初に "C++-clean C"に変換されたコードベースの例であり、その後、徐々にリファクタリングして、より多くのC++機能を使用します。

あなたがしていることに何の問題もありません。 C++コンパイラのコード生成品質に本当に偏執的である場合は、Comeau C++のようなコンパイラを使用できます。このコンパイラは、ターゲット言語としてCにコンパイルされ、Cコンパイラを使用します。このようにして、コードのスポットインスペクションを実行して、C++を使用すると、予期しないパフォーマンス依存のコードが挿入されるかどうかを確認することもできます。ただし、オーバーロードだけの場合は当てはまりません。これは、文字通り、異なる名前の関数が自動的に生成されるためです– IOW、とにかくCで行うことです。

56
Jörg W Mittag

あなたのジレンマに対する客観的な答えは次の要素に依存します:

  1. C++を使用している場合に、実行可能ファイルのサイズが大幅に増加するかどうか。
  2. C++を使用する場合にパフォーマンスに顕著な悪影響があるかどうか。
  3. Cコンパイラのみが使用可能な別のプラットフォームでコードを再利用するかどうか。

いずれかの質問に対する答えが「はい」である場合は、異なるデータ型に対して異なる名前の関数を作成し、Cを使い続ける方がよいでしょう。

すべての質問に対する答えが「いいえ」であれば、C++を使用してはならない理由はわかりません。

15
R Sahu

C++でコンパイルすると、より多くの機能にアクセスできるようになるかのように質問します。これはそうではありません。 C++コンパイラでコンパイルするということは、ソースコードがC++ソースとして解釈され、C++がCの別の言語であることを意味します。もう1つは不足しており、両方の言語で許容できるが異なる解釈のコードを作成することが可能です。

本当に必要なのが関数のオーバーロードだけである場合、C++を組み込むことで問題を混乱させることはできません。同じ名前の異なる関数の代わりに、パラメーターリストを区別して、異なる名前の関数を記述します。

あなたの客観的な基準については、

  1. C++を使用している場合に、実行可能ファイルのサイズが大幅に増加するかどうか。

C++としてコンパイルした場合、実行可能ファイルは、そのように構築された同様のCコードと比較して、わずかに大きくなる可能性があります。少なくとも、C++実行可能ファイルには、シンボルが実行可能ファイル内に保持される範囲で、すべての関数名に対してC++の名前をマングリングするという疑わしい利点があります。 (そして、それが最初にオーバーロードを提供するものです。)差があなたにとって重要であるほど十分に大きいかどうかは、実験によって決定する必要があります。

  1. C++を使用する場合、パフォーマンスに顕著な悪影響があるかどうか。

説明するコードと仮想的な純粋なCのアナログとの間に、顕著なパフォーマンスの違いが見られるとは思いません。

  1. Cコンパイラのみが利用可能な別のプラットフォームでコードを再利用するかどうか。

私はこれに少し異なる光を投げます:C++で構築しているコードをotherコードにリンクする場合は、他のコードもC++で記述して構築する必要がありますC++、または独自のコード( "C"リンケージを宣言)で特別な準備をする必要があります。さらに、オーバーロードされた関数に対してはまったくできません。一方、コードがCで記述され、Cとしてコンパイルされている場合、他のユーザーはCおよびC++モジュールの両方とリンクできます。この種の問題は通常、テーブルを回すことで克服できますが、実際にはC++を必要としないように思われるため、そもそもなぜこのような問題を受け入れるのでしょうか。

13
John Bollinger

昔、C++コンパイラを「より良いC」として使用することは、ユースケースとしてpromotedでした。実際、初期のC++はまさにそれでした。基本的な設計原則は、必要な機能のみを使用でき、使用しなかった機能のコストは発生しないことです。したがって、(overloadキーワードを介してインテントを宣言することによって)関数をオーバーロードすることができ、プロジェクトの残りの部分は正常にコンパイルされるだけでなく、Cコンパイラが生成するよりも悪いコードを生成しません。

それ以来、言語は幾分分かれています。

Cコードのすべてのmallocは、型の不一致エラーになります。動的メモリがない場合は問題ありません。同様に、明示的なキャストを追加する必要があるため、C内のすべてのvoidポインターがトリップします。しかし...なぜそれをしているのですかそのように…C++機能を使用する方法をどんどん導いていきます。

だから、それはいくつかの追加作業で可能になるかもしれません。しかし、それは大規模なC++採用へのゲートウェイとして機能します。数年後に、malloc呼び出しをnewで置き換え、1989年に作成されたように見える古いコードについて人々は不満を言うでしょう。ループ本体のコードのブロックを取り除いて、代わりにアルゴリズムを使用し、コンパイラに許可されていれば、些細なことだったであろう安全でない偽の多態性を非難します。

一方、Cで作成した場合もまったく同じであることを知っているので、C++の代わりにCで作成することは、その存在を考えると間違っていますか?答えが「いいえ」である場合、C++から厳選された機能を使用することも間違いではありません

5
JDługosz

ポリモーフィズムは、C++が無料で提供している本当に優れた機能です。ただし、コンパイラを選択する際に考慮する必要のある他の側面があります。 Cには多態性の代替手段がありますが、その使用は眉毛の隆起を引き起こす可能性があります。その可変関数は 可変関数チュートリアル を参照してください。

あなたの場合、それは次のようなものになります

enum serialDataTypes
{
 INT =0,
 INT_P =1,
 ....
 }Arg_type_t;
....
....
void transmit(Arg_type_t serial_data_type, ...)
{
  va_list args;
  va_start(args, serial_data_type);

  switch(serial_data_type)
  {
    case INT: 
    //Send integer
    break;

    case INT_P:
    //Send data at integer pointer
    break;
    ...
   }
 va_end(args);
}

私はフィリップスのアプローチが好きですが、それはあなたのライブラリをたくさんの呼び出しで散らかします。上記でインターフェースはクリーンです。それには欠点があり、最終的には選択の問題です。

2
nikhil_kotian

多くのプログラミング言語には、その周りに発達する1つ以上の文化があり、言語が「あるべき」であることについて特定のアイデアを持っています。システムプログラミングに適した低水準言語を使用して、そのような目的に適したCの方言を、同様に適切なC++のいくつかの機能で補強することは可能であるはずですが、2つの言語を取り巻く文化はそうではありません。特にこのような合併を支持する。

私は、C++のいくつかの機能を組み込んだ組み込みCコンパイラーを読みました。

foo.someFunction(a,b,c);

本質的に

typeOfFoo_someFunction(foo, a, b, c);

また、静的関数をオーバーロードする機能(名前のマングリングの問題はexported関数がオーバーロードされている場合にのみ発生します)。 Cコンパイラーがリンクおよびランタイム環境に追加の要件を課さずにそのような機能をサポートできないはずがある理由はありませんが、多くのCコンパイラーは、環境負荷を回避するためにC++からのほぼすべてを再帰的に拒否することで、そのようなコストを課さない機能でさえ拒否します。一方、C++の文化は、その言語のすべての機能をサポートできない環境にはほとんど関心がありません。 Cの文化がそのような機能を受け入れるように変更されたり、C++の文化が軽量サブセットの実装を奨励するように変更されたりしない限り、「ハイブリッド」コードは、両方の言語の多くの制限に悩まされがちですが、その能力は制限されます。結合されたスーパーセットで動作する利点。

プラットフォームがCとC++の両方をサポートしている場合、C++をサポートするために必要な追加のライブラリコードによって実行可能ファイルが多少大きくなる可能性がありますが、効果はそれほど大きくありません。 C++内で「C機能」を実行しても、特に影響はありません。より大きな問題は、CおよびC++オプティマイザがさまざまなコーナーケースをどのように処理するかです。 Cのデータ型の動作は、ほとんどがそこに格納されているビットの内容によって定義され、C++には、構造の一般的なカテゴリ(PODS--Plain Old Data Structures)があり、そのセマンティクスは同様にほとんどが格納されているビットによって定義されます。ただし、CとC++の両方の標準では、格納されたビットが意味する動作に反する動作を許可する場合があり、「格納されたビット」動作の例外は2つの言語で異なります。多くの場合、システムプログラミングでは、どちらかの言語の仕様を超える動作の保証が必要ですが、CとC++のカルチャの違いにより、2つの言語の実装では異なる保証が提供される場合があります。

2
supercat

考慮すべきもう1つの重要な要素:コードを継承する人。

その人は常にCコンパイラを使用するCプログラマーになるのですか?そうだと思います。

その人は、C++コンパイラを使用するC++プログラマにもなりますか?私のコードをC++固有のものに依存させる前に、これについて合理的に確認したいと思います。

2
reinierpost

関数のオーバーロードのためだけにC++コンパイラを使用することは悪い習慣ですか?

私見の見解、そうです。私は両方の言語が好きなので、この問題に答えるには統合失調症になる必要がありますが、効率とは関係ありませんが、言語の安全性と慣用的な使用に似ています。

C側

Cの観点からは、関数のオーバーロードを使用するためだけにコードにC++を必要とするのは非常に無駄です。 C++テンプレートで静的なポリモーフィズムにそれを利用しているのでない限り、それは完全に異なる言語への切り替えと引き換えに得られるそのようなささいな構文上の砂糖です。さらに、関数をdylibにエクスポートしたい場合(実用上の懸念があるかもしれないし、そうでない場合もあります)、名前がマングルされたすべてのシンボルを広範囲に消費するために、実際にはそうすることはできません。

C++側

C++の観点からは、CのようなC++を関数のオーバーロードで使用するべきではありません。これは文体的な教義ではなく、日常のC++の実用化に関連したものです。

通常の種類のCコードは、structsのコピートラクターなどを禁止するC型システムに対して作業している場合にのみ、合理的で正気で「安全」に記述できます。 C++のはるかに豊富な型システムで作業を開始すると、memsetmemcpyのような非常に価値の高い日常の関数は、常に頼りになる関数にはなりません。代わりに、それらは一般にペストのように回避したい関数です。C++型の場合、コピーしてシャッフルして解放する生のビットやバイトのように扱うべきではないからです。コードが現在プリミティブとPOD UDTでmemsetのようなものだけを使用している場合でも、誰かが使用するUDTにctorを追加した瞬間(std::unique_ptrメンバーなど、メンバーを必要とするメンバーを追加するだけ)がそのようなものに対して関数または仮想関数またはそのようなものであれば、通常のCスタイルのコーディングのすべてが未定義の動作の影響を受けやすくなります。ハーブ・サッター自身からそれを取りなさい:

memcpyおよびmemcmpは型システムに違反しています。 memcpyを使用してオブジェクトをコピーすることは、コピー機を使用してお金を稼ぐようなものです。 memcmpを使用してオブジェクトを比較することは、ヒョウの斑点を数えて比較することと似ています。ツールとメソッドはその仕事をするように見えるかもしれませんが、それらは許容できるほどには粗すぎます。 C++オブジェクトはすべて情報の非表示に関するものです(おそらくソフトウェアエンジニアリングで最も収益性の高い原則です。項目11を参照してください):オブジェクトはデータを非表示にし(項目41を参照)、コンストラクターと代入演算子を介してそのデータをコピーするための正確な抽象化を考案します(項目52から55を参照) 。これらすべてをmemcpyでブルドーザすることは、情報の隠蔽に関する重大な違反であり、多くの場合、メモリとリソースのリーク(せいぜい)、クラッシュ(悪化)、または未定義の動作(最悪)-C++コーディング標準につながります。

C++でコードを記述している場合、哲学onlyが適用されるため、多くのC開発者はこれに同意しません。あなたはおそらくarememcpyのような関数を常に使用すると非常に問題のあるコードを書くC++としてビルドされるコードでですが、それを行うと完全に問題ありませんC。型システムの違いにより、2つの言語はこの点で大きく異なります。これら2つに共通する機能のサブセットを見て、特にC++側で一方が他方のように使用できると信じるのは非常に魅力的ですが、C +コード(またはC--コード)は一般にCおよびC++コード。

同様に、スローできるC++関数を直接呼び出すことができる場合、Cスタイルのコンテキスト(EHを意味しない)でmallocを使用しないでください。そのメモリをfreeできるようになる前に、Cスタイルのコードの記述を効果的にキャッチできない例外の結果として機能します。したがって、.cppなどの拡張子を持つC++としてビルドされるファイルがあり、mallocmemcpymemsetqsortなどの場合、プリミティブ型でのみ機能するクラスの実装の詳細である場合を除いて、まだ問題が発生していない場合は、さらに問題が発生します。その時点で、例外セーフにするために例外処理を行う必要があります。 。 C++コードを記述している場合は、代わりに一般にRAIIに依存し、vectorunique_ptrshared_ptrなどを使用し、可能な場合はすべての通常のCスタイルのコーディングを避けます。

CとX線のデータ型でかみそりの刃を使って遊んだり、チームに付随的な損傷を引き起こしたりすることなくビットとバイトを使って遊んだりできる理由(ただし、どちらにしても自分自身を傷つける可能性があります)は、Cのせいではありませんタイプcanしますが、何のためにnever can do to can]になります。 Cの型システムを拡張してctor、dtor、vtableなどのC++機能を含め、例外処理を行うと、すべての慣用的なCコードが現在よりもはるかに危険にレンダリングされ、新しい種類のC++に見られるように、完全に異なるスタイルのコーディングを奨励する哲学と考え方の進化。メモリは、たとえばunique_ptrのようなRAII準拠のリソースとは対照的に、メモリを管理するクラスに生のポインタの不正行為を使用することさえ考慮します。そのマインドセットは、絶対的な安全の感覚から進化しませんでした。これは、C++がタイプシステムを通じて単にallowsとすると、例外処理などの機能に対して安全である必要があるものから発展しました。

例外安全

繰り返しになりますが、あなたがC++の世界にいる瞬間、人々はあなたのコードが例外セーフであることを期待するでしょう。コードは既にC++で記述およびコンパイルされているため、将来コードが維持される可能性があり、コードが「想定どおり」であるため、コードによって直接または間接的に呼び出されたコードでstd::vector, dynamic_cast, unique_ptr, shared_ptrなどを使用して無害であると信じる可能性があります。 C++コード。その時点で、物事がスローされる可能性に直面する必要があります。次に、次のような完璧ですばらしいCコードを取得する必要があります。

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        f(n, x); // some function which, now being a C++ function, may 
                 // throw today or in the future.
        ...
        free(x);
        return success;
    }
    return fail;
}

...壊れています。例外セーフになるように書き換える必要があります。

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        try
        {
            f(n, x); // some function which, now being a C++ function, may 
                     // throw today or in the future (maybe someone used
                     // std::vector inside of it).
        }
        catch (...)
        {
            free(x);
            throw;
        }
        ...
        free(x);
        return success;
    }
    return fail;
}

キモい!これが、ほとんどのC++開発者が代わりにこれを要求する理由です。

void some_func(int n, ...)
{
    vector<int> x(n);
    f(x); // some function which, now being a C++ function, may throw today
          // or in the future.
}

上記は、関数がthrowの結果として暗黙の終了をトリガーするコード行をリークしないため、C++開発者が一般に承認する種類のRAII準拠の例外セーフコードです。

言語を選択してください

C++の型システムとRAII、例外安全性、テンプレート、OOPなどの哲学を採用するか、生のビットとバイトを中心に展開するCを採用する必要があります。これらの2つの言語間で不誠実な結婚を形成するのではなく、それらを別々の言語に分離して、それらを一緒にぼかすのではなく、非常に異なる方法で扱う必要があります。

これらの言語はあなたと結婚したいです。あなたは一般的に、両方でデートや浮気をするのではなく、どちらかを選ぶ必要があります。または、私のように一夫多妻になり、両方を結婚させることもできますが、お互いに時間を費やすときは完全に考えを変え、お互いに離れて、お互いに戦わないようにする必要があります。

バイナリサイズ

好奇心から、今すぐフリーリストの実装とベンチマークを取り、C++に移植してみました。

[...] Cコンパイラを使用していないため、Cでどのように表示されるかわからない。

...バイナリサイズがC++としてビルドするだけで膨らむかどうかを知りたいと思っていました。明確なキャストをあちこちに散らす必要がありましたが(実際にアロケーターやCのデータ構造などの低レベルのものを実際に書くのが好きな理由の1つです)、1分しかかかりませんでした。

これは、単純なコンソールアプリのMSVC 64ビットリリースビルドと、C++機能を使用せず、オペレーターのオーバーロードさえも使用しないコードを比較しただけです。Cでビルドした場合と、たとえば<cstdlib>を使用した場合の違いだけです。 <stdlib.h>とそのようなものですが、バイナリサイズとの差がゼロであることに驚きました!

バイナリは、Cでビルドされたときは9,728バイトで、C++コードとしてコンパイルされたときは9,278バイトでした。私は実際にはそれを期待していませんでした。 EHのようなものが少なくともそこに少し追加されると思いました(少なくとも100バイトは異なると思います)が、たぶん私はEH関連の命令を追加する必要がないことを理解できたでしょう。 C標準ライブラリを使用し、何もスローしません。 somethingは、RTTIのように、いずれにしてもバイナリサイズに少し追加されると思いました。とにかく、それを見てとてもクールでした。もちろん、この1つの結果から一般化する必要はないと思いますが、少なくとも少し印象に残っています。また、同じ結果のバイナリサイズは同じ結果の機械語命令も意味すると想像したので、ベンチマークに影響を与えませんでした。

とはいえ、上記の安全性とエンジニアリングの問題でバイナリサイズを気にする人はいますか?もう一度言いますが、言語を選んで、その哲学を受け入れて、それを卑劣にしようとするのではなく、それが私がお勧めすることです。

1
user204677

いくつかの異なるタイプの「関数」を静的にオーバーロードする特定のケースでは、代わりに __Generic_マクロ 機構でC11を使用することを検討できます。あなたの限られたニーズには十分かもしれないと思います。

Philip Kendallの回答 を使用して、次のように定義できます。

_#define transmit(X) _Generic((X),      \
   unsigned char*: transmit_uchar_buffer, \
   char*: transmit_char_buffer,           \
   unsigned char: transmit_uchar,         \
   char: transmit_char) ((X))
_

そしてtransmit(foo)をコード化しますfooのタイプは何でも(上記の4つのタイプの中で)。

[〜#〜] gcc [〜#〜] (および互換性のある Clang )コンパイラのみに関心がある場合は、その ___builtin_types_compatible_p_typeof拡張子付き。