web-dev-qa-db-ja.com

あなたはstd :: vectorを継承してはならない

告白するのは本当に難しいですが、現時点ではstd::vectorを継承したいという強い誘惑があります。

ベクター用にカスタマイズされた約10個のアルゴリズムが必要で、ベクターの直接のメンバーになりたい。しかし、当然、std::vectorの残りのインターフェイスも必要です。さて、法を遵守する市民としての私の最初のアイデアは、MyVectorクラスにstd::vectorメンバーを持つことでした。しかし、その後、std :: vectorのすべてのインターフェイスを手動で再提供する必要があります。入力するのが多すぎます。次に、私はプライベート継承について考えました。そのため、メソッドを提供する代わりに、パブリックセクションにusing std::vector::memberの束を書きます。これも実際には退屈です。

そして、私は本当に、std::vectorから単純にパブリックに継承できると思いますが、ドキュメントでこのクラスを多態的に使用してはならないという警告を提供します。ほとんどの開発者は、これを多形的に使用すべきではないことを理解するのに十分な能力があると思います。

私の決定は絶対に不当ですか?もしそうなら、なぜですか?追加のメンバー実際メンバーを持つが、ベクターのすべてのインターフェースの再入力を伴わない代替手段を提供できますか?私はそれを疑いますが、できれば私はただ幸せになります。

また、いくつかのばかが次のようなものを書くことができるという事実とは別に

std::vector<int>* p  = new MyVector

他に何かありますかrealistic MyVectorの使用に危険がありますか?現実的と言うことで、ベクトルへのポインターを取る関数を想像するようなものを捨てます...

さて、私は私の主張を述べました。私は罪を犯しました。今私を許すかどうかはあなた次第です:)

180
Armen Tsirunyan

実際、std::vectorのパブリック継承には何の問題もありません。これが必要な場合は、それを行ってください。

本当に必要な場合にのみそれを行うことをお勧めします。あなたが自由な機能であなたが望むことをすることができない場合にのみ(例えば、いくつかの状態を維持する必要があります)。

問題は、MyVectorが新しいエンティティであることです。それは、新しいC++開発者がそれを使用する前にそれが何であるかを知る必要があることを意味します。 std::vectorMyVectorの違いは何ですか?あちこちで使用する方が良いですか? std::vectorMyVectorに移動する必要がある場合はどうなりますか? swap()だけを使用してもいいですか?

見栄えを良くするためだけに新しいエンティティを作成しないでください。これらのエンティティ(特に、そのような一般的なもの)は、真空状態では生きられません。エントロピーが絶えず増加する混合環境に住んでいます。

152
Stas

STL全体は、アルゴリズムとコンテナが分離されているとなるように設計されています。

これにより、さまざまなタイプのイテレーターの概念が生まれました:constイテレーター、ランダムアクセスイテレーターなど。

したがって、この規則を受け入れて、作業中のコンテナが何であるかを気にしないようにアルゴリズムを設計するをお勧めします。彼らは彼らの操作を実行する必要があります。

また、 Jeff Attwoodによる良い発言 にリダイレクトさせてください。

89
Kos

std::vectorを一般に継承しない主な理由は、子孫の多態的な使用を効果的に防止する仮想デストラクタがないことです。特に、あなたは 許可されていない to delete a std::vector<T>*であり、実際には派生オブジェクトを指します(派生クラスがメンバーを追加しなくても)それについて警告します。

これらの条件下では、プライベート継承が許可されます。したがって、以下に示すように、親からのプライベート継承と転送に必要なメソッドを使用することをお勧めします。

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::Push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

大多数の回答者が指摘しているように、まずアルゴリズムをリファクタリングして、操作対象のコンテナのタイプを抽象化し、無料のテンプレート関数として残すことを検討する必要があります。これは通常、アルゴリズムに、コンテナーとしてではなく、イテレーターのペアを引数として受け入れることによって行われます。

53
Basilevs

あなたがこれを検討しているなら、あなたは明らかにあなたのオフィスで言語学者をすでに殺しました。邪魔にならないように、どうして

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

これは、誤ってMyVectorクラスをアップキャストすることで生じる可能性のあるすべての失敗を回避し、少しの.vを追加するだけで、すべてのベクトルopsにアクセスできます。

34
Crashworks

何を達成したいですか?いくつかの機能を提供するだけですか?

これを行うC++の慣用的な方法は、機能を実装するいくつかの無料の関数を記述することです。可能性は具体的にはstd :: vectorを実際に必要としない実装する機能のためです。つまり、std :: vectorを継承しようとすることで実際に再利用性を失います。

標準ライブラリとヘッダーを確認し、それらがどのように機能するかを黙想することを強くお勧めします。

19
Karl Knechtel

盲目的に100%従わなければならないルールはほとんどないと思います。あなたはかなり多くのことを考えてきたようですが、これが道だと確信しています。だから-誰かが良いことを考え出さない限りspecificこれをしない理由-あなたの計画を進めるべきだと思う。

13
NPE

std::vectorの定義の隠された詳細を独自の方法で処理するため、またはそのようなクラスのオブジェクトを使用するイデオロギーの理由がない限り、std::vectorとは異なる動作をするクラスを作成したい場合を除き、std::vectorから継承する理由はありませんstd::vectorのもの。ただし、C++の標準の作成者は、特定の方法でベクトルを改善するために、そのような継承されたクラスが利用できるインターフェース(保護されたメンバーの形式)をstd::vectorに提供しませんでした。実際、拡張や追加の実装の微調整が必​​要になる可能性のあるspecificアスペクトについて考える方法がなかったため、これらのインターフェースをあらゆる目的で提供することを考える必要はありませんでした。

std::vectorsはポリモーフィックではないため、2番目のオプションの理由はイデオロギーにすぎず、そうでない場合は、std::vectorのパブリックインターフェイスをパブリック継承またはパブリックメンバーシップで公開しても違いはありません。 (オブジェクトに何らかの状態を保持して、無料の関数で逃げられないようにする必要があるとします)。イデオロギーの観点から見ると、音があまり良くありませんが、std::vectorsは一種の「単純なアイデア」であるため、その場所にある異なるクラスのオブジェクトの形の複雑さは、イデオロギー的には使用されません。

7
Evgeniy

実用的な用語で:派生クラスにデータメンバーがなければ、ポリモーフィックな使用法でも問題はありません。基本クラスと派生クラスのサイズが異なる場合や、仮想関数(vテーブルを意味する)がある場合にのみ、仮想デストラクタが必要です。

ただし、理論上: C++ 0x FCDの[expr.delete]から:最初の選択肢(オブジェクトの削除)で、削除するオブジェクトの静的タイプがその動的タイプと異なる場合、静的型は、削除されるオブジェクトの動的型の基本クラスであり、静的型には仮想デストラクタがあるか、動作が未定義です。

ただし std :: vectorから問題なく派生できます。次のパターンを使用しました。

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::Push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...
3
hmuelner

適切なC++スタイルに従えば、仮想関数がなくても問題ありませんが、slicinghttps://stackoverflow.comを参照)/a/14461532/877329

なぜ仮想機能がないのが問題ではないのですか?関数は、その所有権を持っていないため、受け取るポインターをdeleteしようとするべきではないためです。したがって、厳密な所有権ポリシーに従っている場合、仮想デストラクタは必要ありません。たとえば、これは常に間違っています(仮想デストラクタの有無にかかわらず):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

対照的に、これは常に機能します(仮想デストラクタの有無にかかわらず):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

オブジェクトがファクトリーによって作成された場合、ファクトリーはdeleteの代わりに使用される作業削除者へのポインターも返す必要があります。これは、ファクトリーが独自のヒープを使用する可能性があるためです。呼び出し元は、share_ptrまたはunique_ptrの形式を取得できます。つまり、deleteから直接取得できなかったものをnewしないでください。

3
user877329

はい、安全ではないことをしないように注意している限り、安全です...誰もが新しいベクトルを使用するのを見たことはないと思いますので、実際にはおそらく大丈夫でしょう。ただし、C++の一般的なイディオムではありません。

アルゴリズムとは何かに関する詳細情報を提供できますか?

あるデザインで1つの道を下ってしまい、他の道が見えない場合があります-10の新しいアルゴリズムを使用してベクトル化する必要があると主張しているという事実は、実際には10の一般的な目的がありますか?ベクトルが実装できるアルゴリズム、または汎用ベクトルであると同時にアプリケーション固有の機能を含むオブジェクトを作成しようとしていますか?

私は確かにあなたがこれを行うべきではないと言っているわけではありません、それはあなたが与えた情報が警報ベルを鳴らしているだけで、おそらくあなたの抽象化に何かが間違っていると思い、あなたが何を達成するためのより良い方法があると思います欲しいです。

2
jcoder

ここでは、あなたが望むようにするために、さらに2つの方法を紹介します。 1つはstd::vectorをラップする別の方法、もう1つはユーザーに何かを壊す機会を与えることなく継承する方法です。

  1. 多くの関数ラッパーを作成せずに、std::vectorをラップする別の方法を追加しましょう。
#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. std::vectorの代わりに std :: span から継承し、dtor問題を回避します。
0
JiaHao Xu