私はやむを得ない理由で外出しています。
それはあなたが「強制」をどのように定義するかに依存します。あなたの提案はC++で裸の配列を割り当てる標準的な方法ではないため、これまでに拒否した議論の多くは、ほとんどのC++プログラマーにとって確かに説得力があります。
単純な事実はこれです:はい、あなたは絶対にあなたが説明する方法で物事を行うことができます。あなたが説明していることが機能しない理由はありません。
しかし、繰り返しになりますが、Cで仮想関数を使用できます。時間と労力を費やせば、プレーンCでクラスと継承を実装できます。それらも完全に機能します。
したがって、重要なのは何かが機能するかどうかではありません。しかし、コストについてはもっと詳しく。C++よりもCで継承と仮想関数を実装する方がエラーが発生しやすくなります。Cで実装するには複数の方法があり、互換性のない実装になります。一方、これらはC ++のファーストクラスの言語機能であるため、誰かがその言語が提供するものを手動で実装する可能性はほとんどありません。したがって、すべての継承と仮想関数はC++のルールと連携できます。
これについても同じことが言えます。では、手動のmalloc / freeアレイ管理によるメリットとロスは何ですか?
私がこれから言おうとしていることのどれもがあなたにとって「やむを得ない理由」であるとは言えません。あなたが決心したように見えるので、私はむしろそうなるとは思えません。しかし、記録のために:
パフォーマンス
あなたは次のように主張します:
私が知る限り、後者は前者よりもはるかに効率的であり(メモリをいくつかの非ランダム値に初期化したり、デフォルトのコンストラクターを不必要に呼び出したりしないため)、唯一の違いは、クリーンアップするという事実です。 :
このステートメントは、効率の向上が主に問題のオブジェクトの構築にあることを示唆しています。つまり、どのコンストラクターが呼び出されます。このステートメントは、デフォルトのコンストラクターを呼び出さないことを前提としています。配列を作成するためだけにデフォルトのコンストラクターを使用してから、実際の初期化関数を使用して実際のデータをオブジェクトに配置します。
ええと...それがあなたがやりたいことではない場合はどうなりますか?あなたがしたいことが空の配列を作成することである場合はどうなりますか?それはデフォルトで構築されていますか?この場合、この利点は完全になくなります。
脆弱性
配列内の各オブジェクトには、特殊なコンストラクターなどが必要であり、配列の初期化にはこの種のものが必要であると想定します。しかし、あなたの破壊コードを考慮してください:
for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();
単純なケースでは、これで問題ありません。オブジェクトの数を示すマクロ変数またはconst変数があります。そして、各要素をループしてデータを破棄します。これは簡単な例としては素晴らしいことです。
ここで、例ではなく、実際のアプリケーションについて考えてみましょう。アレイを作成する場所はいくつありますか?数十?数百?for
アレイを初期化するために、一人一人が独自のループを持つ必要があります。for
アレイを破壊するために、一人一人が独自のループを持つ必要があります。
これを一度でも入力ミスすると、メモリが破損する可能性があります。または何かを削除しないでください。または他の恐ろしいことの数。
そして、ここに重要な質問があります。特定の配列について、サイズをどこに保持しますか?作成するすべての配列に割り当てたアイテムの数を知っていますか?各配列には、格納するアイテムの数を知る独自の方法があります。したがって、各デストラクタループはこのデータを適切にフェッチする必要があります。それが間違っている場合...ブーム。
そして、例外安全性があります。これは、まったく新しいワームの缶です。コンストラクターの1つが例外をスローした場合、以前に作成されたオブジェクトを破棄する必要があります。あなたのコードはそれをしません。例外安全ではありません。
ここで、代替案を検討してください。
delete[] my_array;
これは失敗することはできません。それは常にすべての要素を破壊します。配列のサイズを追跡し、例外安全です。したがって、動作することが保証されています。動作しません(で割り当てている限りnew[]
)。
もちろん、配列をオブジェクトでラップできると言うこともできます。それは理にかなっている。配列の型要素でオブジェクトをテンプレート化することもできます。そうすれば、すべてのdesturctorコードは同じです。サイズはオブジェクトに含まれています。そして、たぶん、たぶん、あなたは、ユーザーがメモリが割り当てられる特定の方法をある程度制御する必要があることに気づきます。そうすれば、それはだけではありませんmalloc/free
。
おめでとうございます:あなたはちょうど再発明しましstd::vector
た。
これが、多くのC++プログラマーがもう入力すらしない理由new[]
です。
柔軟性
コードはを使用しmalloc/free
ます。しかし、私がプロファイリングを行っているとしましょう。そして、malloc/free
頻繁に作成される特定のタイプでは、コストが高すぎることを認識しています。私は彼らのために特別なメモリマネージャを作成します。しかし、すべての配列割り当てをそれらにフックする方法は?
そうですね、これらのタイプの配列を作成/破棄する場所をコードベースで検索する必要があります。そして、それに応じて彼らのメモリアロケータを変更する必要があります。そして、他の誰かがそれらのアロケータを元に戻したり、異なるアロケータを使用する新しい配列コードを導入したりしないように、コードベースを継続的に監視する必要があります。
代わりにを使用している場合はnew[]/delete[]
、演算子のオーバーロードを使用できます。演算子new[]
とdelete[]
それらのタイプにオーバーロードを提供するだけです。コードを変更する必要はありません。誰かがこれらの過負荷を回避することははるかに困難です。彼らは積極的にしようとしなければなりません。などなど。
そのため、アロケータが使用されるべき場所で使用されるという柔軟性と合理的な保証が得られます。
読みやすさ
このことを考慮:
my_object *my_array = new my_object[10];
for (int i=0; i<MY_ARRAY_SIZE; ++i)
my_array[i]=my_object(i);
//... Do stuff with the array
delete [] my_array;
これと比較してください:
my_object *my_array = (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE);
if(my_object==NULL)
throw MEMORY_ERROR;
int i;
try
{
for(i=0; i<MY_ARRAY_SIZE; ++i)
new(my_array+i) my_object(i);
}
catch(...) //Exception safety.
{
for(i; i>0; --i) //The i-th object was not successfully constructed
my_array[i-1].~T();
throw;
}
//... Do stuff with the array
for(int i=MY_ARRAY_SIZE; i>=0; --i)
my_array[i].~T();
free(my_array);
客観的に言えば、何が起こっているのかを読み、理解するのが簡単なのはどれですか?
このステートメントを見てください:(my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE)
。これは非常に低レベルのものです。あなたは何の配列も割り当てていません。大量のメモリを割り当てています。オブジェクトのサイズ*必要なオブジェクトの数と一致するように、メモリの塊のサイズを手動で計算する必要があります。キャストも特徴です。
対照的にnew my_object[10]
、物語を語ります。new
「型のインスタンスを作成する」のC++キーワードです。my_object[10]
タイプの10要素配列ですmy_object
。シンプルでわかりやすく、直感的です。キャスティングも、バイトサイズの計算も、何もありません。
この方法では、慣用的に使用する方法を学ぶmalloc
必要があります。この方法では、どのように機能するかを理解する必要があります。それははるかに冗長ではなく、何が起こっているのかがはるかに明白です。malloc
new
new
さらに、malloc
ステートメントの後、実際にはオブジェクトの配列はありません。malloc
C ++コンパイラにオブジェクトへのポインタ(キャスト付き)のふりをするように指示したメモリのブロックを返すだけです。C ++のオブジェクトには有効期間があるため、オブジェクトの配列ではありません。そして、オブジェクトの存続期間は、それが構築されるまで始まりません。そのメモリにはまだコンストラクタが呼び出されていないため、生きているオブジェクトはありません。
my_array
その時点では配列ではありません。それは単なるメモリのブロックです。my_object
次のステップでそれらを構築するまで、それはsの配列にはなりません。これは、新しいプログラマーにとっては信じられないほど直感的ではありません。それらが生きているオブジェクトではなく、注意して扱われるべきであることを知るには、熟練したC ++の手(おそらくCから学んだ人)が必要です。ポインタはまだsmy_object*
を指していないため、まだ適切な動作をしていませmy_object
ん。
対照的に、ケースには生き物がいますnew[]
。オブジェクトが作成されました。彼らは生きていて、完全に形成されています。このポインタは、他のポインタと同じように使用できますmy_object*
。
フィン
上記のいずれも、このメカニズムが適切な状況で潜在的に有用ではないことを示していません。しかし、特定の状況で何かの有用性を認めることは1つのことです。それが物事を行うデフォルトの方法であるべきだと言うのはまったく別のことです。