1

私は最近、この暴言に出くわしました。

この記事で言及されているいくつかの点がよくわかりません。

  • delete著者はvsの小さな煩わしさについて言及していますがdelete[]、ソリューションを提供することなく、(コンパイラにとって) 実際には必要であると主張しているようです。私は何か見落としてますか?
  • function の「特殊なアロケーター」セクションでf()は、割り当てを次のように置き換えることで問題を解決できるようです:(アライメントを省略)

    // if you're going to the trouble to implement an entire Arena for memory,
    // making an arena_ptr won't be much work. basically the same as an auto_ptr,
    // except that it knows which arena to deallocate from when destructed.
    arena_ptr<char> string(a); string.allocate(80);
    // or: arena_ptr<char> string; string.allocate(a, 80);
    arena_ptr<int> intp(a); intp.allocate();
    // or: arena_ptr<int> intp; intp.allocate(a);
    arena_ptr<foo> fp(a); fp.allocate();
    // or: arena_ptr<foo>; fp.allocate(a);
    // use templates in 'arena.allocate(...)' to determine that foo has
    // a constructor which needs to be called. do something similar
    // for destructors in '~arena_ptr()'.
    
  • 「::operator new[] のオーバーロードの危険性」では、作成者はnew(p) obj[10]. 代わりにこれではないのはなぜですか(はるかにあいまいではありません):

    obj *p = (obj *)special_malloc(sizeof(obj[10]));
    for(int i = 0; i < 10; ++i, ++p)
        new(p) obj;
    
  • 「C++ でのメモリ割り当てのデバッグ」。ここで議論することはできません。

記事全体は、カスタム メモリ管理スキームに配置された重要な コンストラクタデストラクタを持つクラスを中心に展開しているようです。それは役に立つかもしれませんし、私はそれについて議論することはできませんが、共通点はかなり限られています.

基本的に、新しい配置とクラスごとのアロケータがあります。これらのアプローチで解決できない問題は何ですか?

また、私が頭が固くて頭がおかしくなった場合に備えて、あなたの理想的な C++ では、何を置き換えoperator newますか? 必要に応じて構文を発明してください - これらの問題をよりよく理解するために、理想的なものは何ですか。

4

2 に答える 2

5

まあ、理想はおそらく、いかなる種類の削除も必要としないことでしょう。ガベージ コレクション環境を用意し、プログラマーが問題全体を回避できるようにします。

暴言の苦情は次のようになります

  1. 「malloc のやり方が気に入った」
  2. 「既知のタイプのオブジェクトを明示的に作成することを強制されるのは好きではありません」

newと の両方を実装しなければならないという厄介な事実については彼は正しいですがnew[]、C のセマンティクスのコアを維持したいという Stroustraps の願望によって、それを強いられています。配列からポインターを知ることはできないため、コンパイラーに自分で伝える必要があります。それを修正することはできますが、そうすることは、言語の C 部分のセマンティクスを根本的に変更することを意味します。あなたはもはやアイデンティティを利用することができませんでした

*(a+i) == a[i]

これは、すべての C コードの非常に大きなサブセットを破壊します。

だから、あなたは言語を持つことができます

  • 配列のより複雑な概念を実装し、ポインター演算の驚異を排除し、ドープベクトルまたは類似のものを使用して配列を実装します。

  • deleteガベージコレクションなので、独自の規律は必要ありません。

つまり、Java をダウンロードできます。次に、言語を変更することでそれを拡張できます。

  • 強く型付けされていないため、アップキャストの型チェックはvoid *省略されます。

...しかし、これは、コンパイラが認識しなくても Foo を Bar に変換するコードを記述できることを意味します。必要に応じて、これによりダックタイピングも有効になります。

問題は、これらのことを完了すると、C っぽい構文を備えた Python または Ruby を取得できるということです。

Stroustrup が cfront 1.0 のテープを送って以来、私は C++ を書いています。現在の C++ に関連する多くの歴史は、C の世界に適合するオブジェクト指向言語を持ちたいという願望から生まれています。Eiffel のように、同時期に登場した、より満足度の高い言語は他にもたくさんありました。C++ が勝ったようです。Cの世界に収まる可能性があるため、勝ったのではないかと思います。

于 2009-04-14T00:05:26.933 に答える
3

暴言、私見は非常に誤解を招くものであり、著者はより細かい詳細を理解しているように思われます。それは彼が誤解を招きたいように見えるだけです。私見、議論の欠陥を示す重要なポイントは次のとおりです。

void* operator new(std::size_t size, void* ptr) throw();

この標準では、上記の関数には次のプロパティがあると定義されています。

戻り値:ptr。

:意図的に他のアクションを実行することはありません。

つまり、この関数は意図的に他のアクションを実行しません。これは、配置newが行うことの鍵であるため、非常に重要です。これは、オブジェクトのコンストラクターを呼び出すために使用され、それだけです。サイズパラメータについても言及されていないことに明示的に注意してください。

時間のない人のために、私のポイントを要約すると、「malloc」がCで行うことはすべて、「::operatornew」を使用してC++で実行できます。唯一の違いは、非集計タイプがある場合、つまり、デストラクタとコンストラクタを呼び出す必要がある型の場合は、それらのコンストラクタとデストラクタを呼び出す必要があります。このような型はCには明示的に存在しないため、「mallocの方が優れている」という引数を使用することは無効です。対応する「destroyMe」で呼び出す必要がある特別な「initializeMe」関数を持つ「C」の構造体がある場合、作成者が作成したすべてのポイントは、非集約C++構造体と同じようにその構造体に適用されます。

彼のポイントのいくつかを明示的に取り上げます:

多重継承を実装するには、コンパイラは実際にいくつかのキャスト中にポインタの値を変更する必要があります。voidに変換するときに最終的にどの値が必要かを知ることはできません*...したがって、通常の関数はC++でmallocの役割を果たすことができません。適切な戻り型はありません。

これは正しくありません。::operatornewはmallocの役割を果たします:

class A1 { };
class A2 { };
class B : public A1, public A2 { };

void foo () {
    void * v = ::operator new (sizeof (B));
    B * b = new (v) B();  // Placement new calls the constructor for B.
    delete v;

    v = ::operator new (sizeof(int));
    int * i = reinterpret_cast <int*> (v);
    delete v'
}

上で述べたように、Bのコンストラクターを呼び出すにはplacement newが必要です。'i'の場合、void*からint*に問題なくキャストできますが、placementnewを再度使用すると型チェックが改善されます。

彼のもう1つのポイントは、位置合わせの要件についてです。

new char [...]によって返されるメモリは、必ずしもstructintlistの配置要件を満たすとは限りません。

3.7.3.1/2の下の標準は言う:

返されるポインタは、任意の完全なオブジェクトタイプのポインタに変換され、割り当てられたストレージ内のオブジェクトまたは配列にアクセスするために使用できるように適切に配置されます(対応する割り当て解除関数の呼び出しによってストレージが明示的に割り当て解除されるまで) 。

それは私にはかなり明確に見えます。

専門のアロケータの下で、著者はあなたが抱えているかもしれない潜在的な問題を説明します。アロケータを、メモリ自体を割り当てるタイプの引数として使用する必要があります。構築されたオブジェクトでは、デストラクタを明示的に呼び出す必要があります。繰り返しますが、これは、アロケータオブジェクトをC構造体の「initalizeMe」呼び出しに渡すこととどのように異なりますか?

デストラクタの呼び出しに関しては、C ++では特別な種類のスマートポインタを簡単に作成できます。これを「placement_pointer」と呼び、スコープ外になったときにデストラクタを明示的に呼び出すように定義できます。その結果、次のことが可能になります。

template <typename T>
class placement_pointer {
  // ...
  ~placement_pointer() {
    if (*count == 0) {
      m_b->~T();
    }
  }
  // ...
  T * m_b;
};

void
f ()
{
  arena a;

  // ...
  foo *fp = new (a) foo;           // must be destroyed
  // ...
  fp->~foo ();

  placement_pointer<foo> pfp = new (a) foo; // automatically !!destructed!!
  // ...
}

私がコメントしたい最後のポイントは次のとおりです。

g ++には、次のように定義された「配置」演算子new[]が付属しています。

inline void *
operator new[](size_t, void *place)
{
  return place;
}

上記のように、この方法で実装するだけでなく、標準で実装する必要があります。

objをデストラクタを持つクラスとします。どこかにsizeof(obj [10])バイトのメモリがあり、その場所にobj型のオブジェクトを10個作成するとします。(C ++はsizeof(obj [10])を10 * sizeof(obj)と定義しています。)この配置演算子new []を使用してこれを行うことができますか?たとえば、次のコードはそうするように見えます。

obj *
f ()
{
  void *p = special_malloc (sizeof (obj[10]));
  return new (p) obj[10];       // Serious trouble...
}

残念ながら、このコードは正しくありません。一般に、演算子new []に渡されるsize_t引数が、割り当てられる配列のサイズに実際に対応するという保証はありません。

しかし、彼が定義を提供することによって強調しているように、サイズ引数は割り当て関数では使用されません。割り当て関数は何もしません。したがって、上記の配置式の唯一の影響は、予想どおりに10個の配列要素のコンストラクターを呼び出すことです。

このコードには他にも問題がありますが、作成者がリストした問題ではありません。

于 2009-04-15T11:02:52.197 に答える