web-dev-qa-db-ja.com

コンテナークラスを実装するときにC ++で何をすべきですか:値または参照によってオブジェクトを格納しますか?

Javaから来たC++は初めてです。

Javaでは、すべての変数(プリミティブを除く)は基本的にポインターです。彼らは「保持」しているもののアドレスを保持しています。

したがって、すべてのJavaデータ構造は参照によってデータを保存します。値によって保存することもできます。つまり、保存したアイテムのコピーを保存して返すこともできますが、追加の作業が必要であり、ネイティブではありません。言語に。

たとえば、コレクションArrayListHashSet、および単純な配列はすべて、実際のアイテムではなく、それらが「保存」するアイテムのアドレスを保存します。

ただし、C++では、選択肢があります。コンテナークラスを実装するときは、値または参照によってユーザーアイテムを格納して返すことができます。

たとえば、ここに私が書いた簡単なStackクラスがあります(無関係なものは省略):

template <typename T> class Stack {
public:
    Stack(...) : ... { }

    void Push(const T& item) {
        if(size == capacity - 1)
            enlargeArray();

        data[indexToInsert++] = &item;
        size++;
    }

    const T& pop() {
        const T& item = *data[indexToInsert - 1];
        data[indexToInsert - 1] = 0;
        indexToInsert--;
        size--;
        return item;
    }

    int getSize() const {
        return size;
    }
private:
    const T** data;
    int indexToInsert;
    int size;
    int capacity;

    void enlargeArray() {
        // omitted
    }
};

このデータ構造は、参照によってデータを受け取り、返します。 Pushはconst参照を受け取り、popはconst参照を返します。バッキング配列は、オブジェクトではなくポインタの配列です。

ただし、Pushも次のようになります。

    void Push(T item) {
        if(size == capacity - 1)
            enlargeArray();

        data[indexToInsert++] = item;
        size++;
    }

また、popは、const T&ではなく、Tを返す可能性があります。

私の質問は、C++で推奨されるアプローチは何ですか? Is推奨されるアプローチはありますか? 「コンテナ」クラスを実装する場合、通常どのアプローチを取るべきですか?

3
Aviv Cohn

まず、おそらくコンテナークラスを実装しないでください。 95%の確率で標準ライブラリに含める必要があります。学びたいだけの場合、または5%以内の場合は、続けてください。

テンプレートを定義する場合は、ユーザーに決定を任せます。ユーザーは以下を使用できます。

Stack<Foo>値によって必要な場合。 Stack<Foo*>ポインタで必要な場合。 Stack<std::unique_ptr<Foo>>自分自身をクリーンアップするポインタが必要な場合。

どちらを使用するかを選択するときは、別のことをする十分な理由がない限り、デフォルトで値を使用する必要があります。スタッククラス内では、すべてを値で格納します。テンプレートの使用で間接参照が必要な場合は、T =ポインター型を使用できます。

コードを見る:

void Push(const T& item) {
    if(size == capacity - 1)
        enlargeArray();

    data[indexToInsert++] = &item;
    size++;
}

それはできません。 &itemは、渡されたものへのポインターを記録します。ただし、ポインターが有効な期間はわかりません。プッシュ終了直後は無効になる場合があります。その場合、無効な場所へのポインタを保存しました。一般に、ポインタが有効であるとは限りません。代わりにアイテムをコピーする必要があります。

11
Winston Ewert

私の質問は、C++で推奨されるアプローチは何ですか?推奨されるアプローチはありますか? 「コンテナ」クラスを実装する場合、通常どのアプローチを取るべきですか?

C++では、次の方法でオブジェクトを保持できます。

  • 参照
  • ポインタ
  • スマートポインター(std :: unique_ptr、std :: shared_ptr、YourPointerClass)。 (最後の2つについては言及していません)。

これらはそれぞれ、さまざまな状況で有効であり、オブジェクトの所有権、寿命の管理、および多態性の振る舞いに関してさまざまな制約を課します(つまり、推奨されるアプローチはありません-それらの多くがあります:)):

  • 保存を使用値によるの場合:

    • あなたはポリモーフィックな動作でオブジェクトを保存しない(つまり、オブジェクトは基本クラスと仮想関数のない具体的なクラスであるか、すべてのオブジェクトが同じランタイム型である-基本クラスの特殊化を保存していない)
    • コンテナーはオブジェクトを所有します(つまり、含まれるオブジェクトの存続期間はコンテナーの存続期間と同じでなければならず、コンテナーが破棄されると破棄されます)
  • 保存を使用参照によりの場合:

    • 保存されたオブジェクトはコンテナが所有していない
    • 含まれるオブジェクトは、コンテナよりもスコープと有効期間が長くなります。
    • ポリモーフィックな動作に関心がある(この場合は、基本クラスへの参照を格納する必要があります)通常は、割り当てでスライスする可能性があるため、これを実行しないでください(クラス階層がポリモーフィックな割り当てをサポートしている場合を除きます-これは、別のディスカッションです)
  • 生のポインタによる格納を使用する場合:

    • 保存されたオブジェクトがポリモーフィックな動作(基本クラスまたは仮想関数、あるいはその両方)を公開している
    • コンテナ保存されたオブジェクトを所有していません
  • スマートポインタによる格納を使用する場合:

    • 保存されたオブジェクトは多態性の動作を公開します
    • オブジェクトがコンテナ(std :: unique_ptr)によって所有されているか、所有権が共有されているか(std :: shared_ptr)、またはスマートポインタの使用がクライアントコードの制約によって課されています。
    • 保存されたオブジェクトはコピー/インスタンス化にコストがかかります(編集@NirFriedmanを参照)

これらすべてをサポートするには、おそらく、クライアントコードで、格納された型によってクラスをテンプレート化し、必要に応じて入力する必要があります。

4
utnapistim

コンテナがオブジェクトを「所有」していて、それらが基本クラスであるかどうかに依存します。

  1. コンテナーがオブジェクトを所有していない場合は、ポインターを使用してダングラーに注意する必要があります(オブジェクトが破棄されるときに、コンテナーがオブジェクトへのポインターを保持していないことを確認してください)。

  2. コンテナーがオブジェクトを所有し、基本クラスでない場合は、値で格納する必要があります。

  3. それ以外の場合(コンテナは所有し、サブクラスがあります)std::unique_ptrを使用して保存し、保存時にスライスが発生しないようにし、適切に破棄する必要があります。

0
ratchet freak