1. ホーム
  2. c++

[解決済み] STLコンテナからデリゲートではなく、実装を継承することは問題ないのでしょうか?

2023-06-30 01:47:48

質問

私はドメイン固有のオブジェクトのコンテナをモデル化するためにstd::vectorを適応させるクラスを持っています。私は、std::vector API の大部分をユーザーに公開し、コンテナー上でおなじみのメソッド (size, clear, at, etc...) と標準アルゴリズムを使用できるようにしたいと思っています。これは、私のデザインで繰り返されるパターンのようです。

class MyContainer : public std::vector<MyObject>
{
public:
   // Redeclare all container traits: value_type, iterator, etc...

   // Domain-specific constructors
   // (more useful to the user than std::vector ones...)

   // Add a few domain-specific helper methods...

   // Perhaps modify or hide a few methods (domain-related)
};

実装のためにクラスを再利用する場合、継承よりも合成を好むという慣習は承知しています -- しかし、限界があるはずです! もし私がすべてを std::vector に委譲するとしたら、(私の計算では) 32 の転送関数があることになります!

そこで質問です... このようなケースで実装を継承することは本当に悪いことなのでしょうか?どのようなリスクがあるのでしょうか?それほど多くのタイピングをせずに実装できる、より安全な方法はありますか?実装継承を使うなんて、私は異端なんでしょうか?)

編集してください。

std::vector<>ポインタを介してMyContainerを使用してはいけないことを明確にするのはどうでしょうか。

// non_api_header_file.h
namespace detail
{
   typedef std::vector<MyObject> MyObjectBase;
}

// api_header_file.h
class MyContainer : public detail::MyObjectBase
{
   // ...
};

boostのライブラリは、いつもこんなことをやっているようです。

2を編集します。

提案の1つは、自由な関数を使うことでした。ここに擬似コードとして示しておきます。

typedef std::vector<MyObject> MyCollection;
void specialCollectionInitializer(MyCollection& c, arguments...);
result specialCollectionFunction(const MyCollection& c);
etc...

よりOOな方法です。

typedef std::vector<MyObject> MyCollection;
class MyCollectionWrapper
{
public:
   // Constructor
   MyCollectionWrapper(arguments...) {construct coll_}

   // Access collection directly
   MyCollection& collection() {return coll_;} 
   const MyCollection& collection() const {return coll_;}

   // Special domain-related methods
   result mySpecialMethod(arguments...);

private:
   MyCollection coll_;
   // Other domain-specific member variables used
   // in conjunction with the collection.
}

どのように解決するのですか?

リスクはベースクラスへのポインタを介してのデアロケートです。 ( 削除 , 削除[] および潜在的に他のデアロケーションメソッド)。 これらのクラス( deque , マップ , 文字列 など)は仮想ドトールを持たないので、それらのクラスへのポインタだけでは適切にクリーンアップすることは不可能です。

struct BadExample : vector<int> {};
int main() {
  vector<int>* p = new BadExample();
  delete p; // this is Undefined Behavior
  return 0;
}

とはいえ もし を使うのであれば、それを継承することに大きな欠点はありませんが、場合によってはそれは大きな欠点になります。 他の欠点としては、実装の仕様や拡張機能 (予約された識別子を使用しないものもあります) との衝突や、肥大化したインターフェース () の扱いがあります。 文字列 特に)。 のようなコンテナアダプタのように、場合によっては継承が意図されていることもあります。 スタック のようなコンテナアダプタには protected メンバである c (それらが適応する基礎となるコンテナ) を持ち、それはほとんど派生クラスのインスタンスからしかアクセスできません。

継承と合成のどちらかの代わりに 自由な関数を書くことを検討してください。 イテレータペアまたはコンテナ参照のいずれかを取り、それを操作する自由な関数を書くことを検討してください。 実質的にすべての <algorithm> はこの例です。 make_heap , pop_heap そして push_heap は、特にドメイン固有のコンテナの代わりに自由な関数を使用する例です。

つまり、データ型にはコンテナクラスを使用し、ドメイン固有のロジックには自由関数を呼び出すのです。 しかし、typedefを使用することで、モジュール化を達成することができ、宣言を簡素化することができ、また、その一部が変更される必要がある場合、単一のポイントを提供することができます。

typedef std::deque<int, MyAllocator> Example;
// ...
Example c (42);
example_algorithm(c);
example_algorithm2(c.begin() + 5, c.end() - 5);
Example::iterator i; // nested types are especially easier

value_type と allocator は typedef を使った後のコードに影響を与えることなく変更可能であり,コンテナさえも デキュー から ベクトル .