17

私のチームに新しく到着した開発者と話し合った後、C ++には、C構造の方が優れていると思われるため、C構造を使用する習慣があることに気付きました(つまり、より速く、よりスリムで、よりきれいで、理由を選択してください)。

同様のC++構造と比較して、C構造を示す、共有する価値のある例は何ですか?

それぞれの例について、C++コンストラクトが元のCコンストラクトと同じかそれ以上である理由を読む必要があります。目的は、C ++コードでやや危険/安全でないと見なされる一部のC構造の代替手段を提供することです(C ++ 0xのみとして明確にマークされている限り、C ++ 0xの有効な回答のみが受け入れられます)。

例として、回答(構造体インライン初期化)を以下に投稿します。

注1:ケースごとに1つの回答をお願いします。複数のケースがある場合は、複数の回答を投稿してください

注2:これはCの質問ではありません。この質問に「C」タグを追加しないでください。 これは、C ++とCの間の戦いになることは想定されていません。C++のCサブセットの一部の構成と、他のC++「ツールキット」でのそれらの代替の研究のみです。

注3:これはCバッシングの質問ではありません。理由が欲しい。自慢、バッシング、および証明されていない比較はダウンモッドされます。Cに相当するものがないC++機能について言及することは、トピックから外れていると見なすことができます。C++機能に対してC機能を並べて配置する必要があります。

4

21 に答える 21

34

RAII とそれに続くすべての栄光 vs. 手動リソースの取得/解放

C:

Resource r;
r = Acquire(...);

... Code that uses r ...

Release(r);

例として、Resourceメモリへのポインタで、Acquire/Release がそのメモリを割り当て/解放するか、Acquire/Release がそのファイルをオープン/クローズするオープン ファイル記述子である可能性があります。

これにはいくつかの問題があります。

  1. 電話するのを忘れるかもしれませんRelease
  2. のデータ フローに関する情報rは、コードによって伝達されません。が同じスコープ内で取得およびリリースされた場合r、コードはこれを自己文書化しません。
  3. Resource rとの間の時間は、初期化されていませんがr.Acquire(...)r実際にはアクセス可能です。これがバグの元です。

RAII (Resource Acquisition Is Initialization) 手法を適用すると、C++ で次のようになります。

class ResourceRAII
{
  Resource rawResource;

  public:
  ResourceRAII(...) {rawResource = Acquire(...);}
  ~ResourceRAII() {Release(rawResource);}

  // Functions for manipulating the resource
};

...

{
  ResourceRAII r(...);

  ... Code that uses r ...
}

C++ バージョンでは、リソースの解放を忘れないようにすることができます (そうすると、メモリ リークが発生しますが、これはデバッグ ツールによってより簡単に検出されます)。これにより、プログラマはリソースのデータ フローがどのように流れるかを明示する必要があります (つまり、リソースが関数のスコープ中にのみ存在する場合、これはスタック上の ResourceRAII の構築によって明確になります)。リソースオブジェクトの作成とその破棄の間に、リソースが無効になるポイントはありません。

また、例外安全です。

于 2008-10-22T19:04:22.723 に答える
27

マクロとインライン テンプレート

C スタイル:

#define max(x,y) (x) > (y) ? (x) : (y)

C++ スタイル

inline template<typename T>
const T& max(const T& x, const T& y)
{
   return x > y ? x : y;
}

C++ アプローチを好む理由:

  • 型の安全性 -- 引数が同じ型でなければならないことを強制します
  • max の定義の構文エラーは、マクロを呼び出す場所ではなく、正しい場所を指します。
  • 関数にデバッグできます
于 2008-10-22T18:31:53.380 に答える
18

動的配列とSTLコンテナー

Cスタイル:

int **foo = new int*[n];
for (int x = 0; x < n; ++x) foo[x] = new int[m];
// (...)
for (int x = 0; x < n; ++x) delete[] foo[x];
delete[] foo;

C ++スタイル:

std::vector< std::vector<int> > foo(n, std::vector<int>(m));
// (...)

STLコンテナが優れている理由:

  • サイズ変更可能で、配列のサイズは固定されています
  • それらは例外安全です-未処理の例外が(...)部分で発生すると、配列メモリがリークする可能性があります-コンテナはスタック上に作成されるため、アンワインド中に適切に破棄されます
  • それらは境界チェックを実装します。例:vector :: at()(配列の境界から外れると、アクセス違反が発生し、プログラムが終了する可能性があります)
  • それらは使いやすいです、例えば、vector :: clear()対手動で配列をクリアする
  • それらはメモリ管理の詳細を隠し、コードをより読みやすくします
于 2008-10-22T19:27:50.587 に答える
17

#define vs. const

私は長い間Cをコーディングしてきた開発者からこのようなコードを見続けています:

#define MYBUFSIZE 256

.  .  . 

char somestring[MYBUFSIZE];

などなど。

C ++では、これは次のようになります。

const int MYBUFSIZE = 256;

char somestring[MYBUFSIZE];

もちろん、開発者がchar配列の代わりにstd :: stringを使用する方がよいでしょうが、それは別の問題です。

Cマクロの問題は非常に多く、この場合、型チェックは大きな問題ではありません。

私が見たところ、これはCプログラマーがC++に変換して壊すのは非常に難しい習慣のようです。

于 2008-10-22T19:32:16.037 に答える
14

デフォルトのパラメータ:

子:

void AddUser(LPCSTR lpcstrName, int iAge, const char *lpcstrAddress);
void AddUserByNameOnly(LPCSTR lpcstrName)
  {
  AddUser(lpcstrName, -1,NULL);
  }

C++ の代替品/同等品:

void User::Add(LPCSTR lpcstrName, int iAge=-1, const char *lpcstrAddress=NULL);

改善の理由:

プログラマーは、より少ない行数のソース コードで、よりコンパクトな形式で、プログラムの関数を表現することができます。また、未使用のパラメーターのデフォルト値を、実際に使用される場所に最も近い値で表現できるようにします。呼び出し元に対して、クラス/構造体へのインターフェイスを簡素化します。

于 2008-10-22T18:50:08.710 に答える
13

C のqsort関数と C++ のsort関数テンプレート。後者は、明白な結果とあまり明白でない結果を持つテンプレートを介してタイプ セーフを提供します。

  • 型安全性により、コードのエラーが発生しにくくなります。
  • のインターフェースsortは少し簡単です (要素のサイズを指定する必要はありません)。
  • コンパイラは、比較関数の型を認識しています。関数ポインターの代わりに、ユーザーが関数オブジェクトを渡すと、比較のインライン化が簡単になるため、より高速sortに実行されます。これは、C バージョンで必要な関数ポインターには当てはまりません。qsort

次の例は、 の C スタイルの配列qsortに対する vsの使用法を示しています。sortint

int pint_less_than(void const* pa, void const* pb) {
    return *static_cast<int const*>(pa) - *static_cast<int const*>(pb);
}

struct greater_than {
    bool operator ()(int a, int b) {
        return a > b;
    }
};

template <std::size_t Size>
void print(int (&arr)[Size]) {
    std::copy(arr, arr + Size, std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;
}

int main() {
    std::size_t const size = 5;
    int values[] = { 4, 3, 6, 8, 2 };

    { // qsort
        int arr[size];
        std::copy(values, values + size, arr);
        std::qsort(arr, size, sizeof(int), &pint_less_than);
        print(arr);
    }

    { // sort
        int arr[size];
        std::copy(values, values + size, arr);
        std::sort(arr, arr + size);
        print(arr);
    }

    { // sort with custom comparer
        int arr[size];
        std::copy(values, values + size, arr);
        std::sort(arr, arr + size, greater_than());
        print(arr);
    }
}
于 2008-10-22T20:09:31.267 に答える
8

構造体インライン初期化とインラインコンストラクター

C ++では、データの単純な集約が必要になる場合があります。データはある程度独立しているため、カプセル化によってデータを保護することは、努力する価値がありません。

// C-like code in C++
struct CRect
{
   int x ;
   int y ;
} ;

void doSomething()
{
   CRect r0 ;               // uninitialized
   CRect r1 = { 25, 40 } ;  // vulnerable to some silent struct reordering,
                            // or adding a parameter
}

; 上記のコードには3つの問題があります。

  • オブジェクトが具体的に初期化されていない場合、すべてが初期化されることはありません
  • (何らかの理由で)xまたはyを変更すると、doSomething()のデフォルトのC初期化が正しくなくなります。
  • azメンバーを追加し、デフォルトで「ゼロ」にするのが好きな場合でも、すべてのインライン初期化を変更する必要があります

以下のコードでは、コンストラクターがインライン化されているため(実際に役立つ場合)、コストはゼロになります(上記のCコードのように)。

// C++
struct CRect
{
   CRect() : x(0), y(0) {} ;
   CRect(int X, int Y) : x(X), y(Y) {} ;
   int x ;
   int y ;
} ;

void doSomething()
{
   CRect r0 ;
   CRect r1(25, 40) ;
}

(ボーナスは、operator ==メソッドを追加できることですが、このボーナスはトピックから外れているため、言及する価値はありますが、答えとしては価値がありません。)

編集:C99は初期化された名前を付けました

Adam Rosenfieldは、私が非常に興味深いと思う興味深いコメントをしました。

C99では、名前付き初期化子を使用できます 。CRect r = {.x = 25、.y = 40}

これはC++ではコンパイルされません。C互換性のためだけなら、これをC++に追加する必要があると思います。とにかく、Cでは、この回答で言及されている問題を軽減します。

于 2008-10-22T17:49:45.213 に答える
7

iostream vs stdio.h

Cの場合:

#include <stdio.h>

int main()
{
    int num = 42;

    printf("%s%d%c", "Hello World\n", num, '\n');

    return 0;
}

フォーマット文字列は実行時に解析されるため、タイプセーフではありません。

C ++の場合:

#include <iostream>

int main()
{
    int num = 42;

    std::cout << "Hello World\n" << num << '\n';
}

データ型はコンパイル時に既知であり、フォーマット文字列が必要ないため、入力する必要も少なくなります。

于 2008-10-22T19:22:05.620 に答える
5

C コンストラクトを置き換える C++ コンストラクトでの fizzer の投稿に続いて、ここに私の答えを書きます。

警告: 以下で提案されている C++ ソリューションは標準 C++ ではありませんが、g++ および Visual C++ の拡張であり、C++0x の標準として提案されています(これに関するFizzerのコメントに感謝します)。

Johannes Schaub - litb の回答では、別の C++03 準拠の方法が提供されていることに注意してください。

質問

C配列のサイズを抽出するには?

提案されたCソリューション

出典: C++ マクロが役立つのはいつですか?


#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

現在のスレッドで議論されている「優先」テンプレート ソリューションとは異なり、定数式として使用できます。

char src[23];
int dest[ARRAY_SIZE(src)];

定数式を生成できるテンプレート化されたソリューションがあるため、Fizzer には同意しません (実際、テンプレートの非常に興味深い部分は、コンパイル時に定数式を生成する機能です)。

とにかく、ARRAY_SIZE は C 配列のサイズを抽出できるマクロです。C++ のマクロについては詳しく説明しません。目的は、同等またはより優れた C++ ソリューションを見つけることです。

より良い C++ ソリューションですか?

次の C++ バージョンにはマクロの問題はなく、同じように何でもできます。

template <typename T, size_t size>
inline size_t array_size(T (&p)[size])
{
   // return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

デモンストレーション

次のコードで示されているように:

#include <iostream>

// C-like macro
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

// C++ replacement
template <typename T, size_t size>
inline size_t array_size(T (&p)[size])
{
   // return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

int main(int argc, char **argv)
{
   char src[23];
   char * src2 = new char[23] ;
   int dest[ARRAY_SIZE(src)];
   int dest2[array_size(src)];

   std::cout << "ARRAY_SIZE(src)  : " << ARRAY_SIZE(src) << std::endl ;
   std::cout << "array_size(src)  : " << array_size(src) << std::endl ;
   std::cout << "ARRAY_SIZE(src2) : " << ARRAY_SIZE(src2) << std::endl ;
   // The next line won't compile
   //std::cout << "array_size(src2) : " << array_size(src2) << std::endl ;

   return 0;
}

これは出力されます:

ARRAY_SIZE(src)  : 23
array_size(src)  : 23
ARRAY_SIZE(src2) : 4

上記のコードでは、マクロがポインターを配列と間違えたため、間違った値 (23 ではなく 4) が返されました。代わりに、テンプレートはコンパイルを拒否しました。

/main.cpp|539|error: no matching function for call to ‘array_size(char*&)’|

* コンパイル時に定数式を生成できる * 間違った方法で使用した場合、コンパイルを停止できる

結論

したがって、全体として、テンプレートの引数は次のとおりです。

  • コードのマクロのような汚染はありません
  • 名前空間内に隠すことができます
  • 間違った型評価から保護できます (メモリへのポインタは配列ではありません)

注: Microsoft による C++ 用の strcpy_s の実装に感謝します...これがいつか役に立てることはわかっていました... ^_^

http://msdn.microsoft.com/en-us/library/td1esda9.aspx

編集:ソリューションはC++ 0x用に標準化された拡張機能です

Fizzer は、これは現在の C++ 標準では無効であると正しくコメントしましたが、これはまったく真実でした (-pedantic オプションをオンにして g++ で検証できたため)。

それでも、これは現在 2 つの主要なコンパイラ (つまり、Visual C++ と g++) で使用できるだけでなく、次のドラフトで提案されているように、C++0x についても考慮されていました。

C++0x の唯一の変更点は、おそらく次のようなものです。

inline template <typename T, size_t size>
constexpr size_t array_size(T (&p)[size])
{
   //return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

( constexprキーワードに注意してください)

編集 2

Johannes Schaub - litb の回答は、別の C++03 準拠の方法を提供しています。参照用にここにソースをコピーして貼り付けますが、完全な例については彼の回答にアクセスしてください(そしてそれをupmodしてください!):

template<typename T, size_t N> char (& array_size(T(&)[N]) )[N];

次のように使用されます。

int p[] = { 1, 2, 3, 4, 5, 6 };
int u[sizeof array_size(p)]; // we get the size (6) at compile time.

私の脳内の多くのニューロンは、array_size(ヒント: N 文字の配列への参照を返す関数です) の性質を理解するために揚げました。

:-)

于 2008-10-22T20:25:45.273 に答える
4

C の方法(type)static_cast<type>(). トピックについては、stackoverflow のあちこちを参照してください

于 2008-10-22T20:25:45.337 に答える
4

ローカル (自動) 変数の宣言

(Jonathan Leffler が正しく指摘しているように、C99 以降は正しくありません)

C では、定義されているブロックの先頭ですべてのローカル変数を宣言する必要があります。

C++ では、使用する前に変数の定義を延期することが可能です (そしてそれが望ましい)。次の 2 つの主な理由から、後で行うことをお勧めします。

  1. これにより、プログラムの明確さが向上します (初めて使用される変数の型が表示されるため)。
  2. これにより、リファクタリングが容易になります (まとまりのある小さなコード チャンクがあるため)。
  3. これにより、プログラムの効率が向上します (実際に必要なときに変数が構築されるため)。
于 2008-10-23T13:07:36.467 に答える
2

おそらく豊富に明らかな名前空間を提供します。

cの混雑したグローバルスコープ:

void PrintToScreen(const char *pBuffer);
void PrintToFile(const char *pBuffer);
void PrintToSocket(const char *pBuffer);
void PrintPrettyToScreen(const char *pBuffer);

対。

グローバルスコープ、名前空間のc ++の定義可能なサブディビジョン:

namespace Screen
{
   void Print(const char *pBuffer);
}

namespace File
{
   void Print(const char *pBuffer);
}

namespace Socket
{
   void Print(const char *pBuffer);
}

namespace PrettyScreen
{
   void Print(const char *pBuffer);
}

これは少し不自然な例ですが、定義したトークンを意味のあるスコープに分類する機能により、関数の目的と呼び出されるコンテキストを混同することを防ぎます。

于 2008-12-31T02:50:29.033 に答える
2

関数がまだ定数式を返すことができないという制限を回避するために、可変長配列を使用するpaercebalの構造に従って、特定の他の方法でそれを行う方法を次に示します。

template<typename T, size_t N> char (& array_size(T(&)[N]) )[N];

私は他の回答のいくつかにそれを書きましたが、このスレッドよりも適切な場所はありません。さて、これを使用する方法は次のとおりです。

void pass(int *q) {
    int n1 = sizeof(q); // oops, size of the pointer!
    int n2 = sizeof array_size(q); // error! q is not an array!
}

int main() {
    int p[] = { 1, 2, 3, 4, 5, 6 };
    int u[sizeof array_size(p)]; // we get the size at compile time.

    pass(p);
}

sizeof に対する利点

  1. 非配列では失敗します。ポインターに対してサイレントに動作しませ
  2. 配列サイズが取得されることをコードで伝えます。
于 2009-01-04T20:12:52.800 に答える
2

Alex Cheへの応答として、また C への公平性を期して:

C の現在の ISO 標準仕様である C99 では、変数は C++ と同様にブロック内の任意の場所で宣言できます。次のコードは有効な C99 です。

int main(void)
{
   for(int i = 0; i < 10; i++)
      ...

   int r = 0;
   return r;
}
于 2008-10-29T00:33:40.927 に答える
1

std::copy対。memcpy

まず、ユーザビリティに関する懸念があります。

  • memcpyvoid ポインターを取ります。これにより、型の安全性が失われます。
  • std::copy特定の場合に範囲の重複を許可しますが (std::copy_backward他の重複する場合には存在します)、memcpy決して許可しません。
  • memcpyはポインターでのみ機能しますが、std::copyイテレーターで機能します (ポインターは特殊なケースであるためstd::copy、ポインターでも機能します)。これは、std::copyたとえばstd::list.

確かに、この余分な安全性と汎用性にはすべて代償が伴いますよね?

測定したところ、std::copyは よりもわずかにパフォーマンスが優れているmemcpyことがわかりました。

memcpyつまり、実際の C++ コードで使用する理由がないように思えます。

于 2012-05-05T17:36:03.083 に答える
0

C では、動的機能の多くは、関数ポインターを渡すことによって実現されます。C++ では関数オブジェクトを使用できるため、柔軟性と安全性が向上します。Stephen Dewhurst の優れたC++ Common Knowledgeを基にした例を紹介します。

C 関数ポインター:

int fibonacci() {
  static int a0 = 0, a1 =1; // problematic....
  int temp = a0;
  a0 = a1;
  a1 = temp + a0;
  return temp;
}

void Graph( (int)(*func)(void) );
void Graph2( (int)(*func1)(void), (int)(*func2)(void) ); 

Graph(fibonacci);
Graph2(fibonacci,fibonacci);

関数 内の静的変数が与えられた場合、およびfibonacci()の実行順序によって動作が変わることがわかります。ただし、 を呼び出すたびに予期しない結果が生じる可能性があり、一連の次の値ではなく、およびが生成されます。呼び出される関数に関して、シリーズの個々のインスタンスの次の値。(明らかに、関数の状態を外部化することもできますが、ユーザーを混乱させ、クライアント関数を複雑にすることは言うまでもなく、それは要点を逃します)GraphGraph2()Graph2()func1func2

C++ 関数オブジェクト:

class Fib {
  public:
    Fib() : a0_(1), a1_(1) {}
    int operator();
  private:
    int a0_, a1_;
};
int Fib::operator() {
    int temp = a0_;
    a0_ = a1_;
    a1_ = temp + a0_;
    return temp;
}


template <class FuncT>
void Graph( FuncT &func );

template <class FuncT>
void Graph2( FuncT &func1, FuncT &func2); 

Fib a,b,c;
Graph(a);
Graph2(b,c);

ここで、Graph()およびGraph2()関数の実行順序は、呼び出しの結果を変更しません。また、それらが使用されると、別の状態を呼び出してGraph2() b維持cします。それぞれが完全なフィボナッチ数列を個別に生成します。

于 2008-12-31T03:23:20.107 に答える
0

バランスを保つために、この投稿には、C++ スタイルの同等のものよりも優れている場合がある C スタイルの構成の例があります。

于 2008-10-22T19:57:45.360 に答える
0

オーバーロードされた関数:

子:

AddUserName(int userid, NameInfo nameinfo);
AddUserAge(int userid, int iAge);
AddUserAddress(int userid, AddressInfo addressinfo);

C++ の同等/置換:

User::AddInfo(NameInfo nameinfo);
User::AddInfo(int iAge);
User::AddInfo(AddressInfo addressInfo);

改善の理由:

関数の概念が名前で表現され、パラメーターの型がパラメーター自体でのみ表現されるように、プログラマーがインターフェイスを表現できるようにします。呼び出し元が概念の表現により近い方法でクラスと対話できるようにします。また、一般に、より簡潔でコンパクトで読みやすいソース コードになります。

于 2008-10-24T16:12:46.487 に答える
0

C++ の new と C の malloc の比較 (メモリ管理用)

new 演算子ではクラス コンストラクターを呼び出すことができますが、malloc ではできません。

于 2009-01-04T20:39:59.003 に答える
0

iostream

フォーマットされた I/O は、C ランタイムを使用すると高速になる場合があります。しかし、低レベルの I/O (読み取り、書き込みなど) がストリームで遅くなるとは思いません。相手がファイル、文字列、ソケット、またはユーザー定義のオブジェクトであるかどうかを気にせずにストリームを読み書きできる機能は、非常に便利です。

于 2008-10-24T21:16:44.673 に答える
-4

のほぼすべての用途void*

于 2008-10-22T18:00:39.467 に答える