生配列やvector等の要素を枚挙することは多いと思います。
その延長線上で、異なる型のコンテナの要素をポリモーフィズム的に枚挙したいのですが、最近のC++なら簡単と思っていたら、意外に難しいです。
しかし、実は私がまだ把握できていない標準ライブラリやboost等にそのような機能をサポートしたクラスがありそうな気もしています。
そのようなクラスをご存知の方、もしくは、簡単な方法をご存知の方がいましたら、是非、ご教授頂けないでしょうか?
詳細
例えば、下記のようなコンテナaFooArray[]とstd::vector<Bar>があるとします。
struct ElementBase
{
virutal ~ElementBase() { }
virtual char const* getName() = 0;
};
struct Foo : public ElementBase
{
std::string const mName;
char const* getName()
{
return mName.c_str();
}
:
};
Foo aFooArray[]={ ... };
struct Bar : public ElementBase
{
char const* mName;
char const* getName()
{
return mName;
}
:
};
std::vector<Bar> aBarVector={ ... };
boost:any的なテクニックでそれぞれへのポインタを管理用コンテナに入れてます。
イメージは下記です。(実際にビルドしてないので雰囲気だけ。)
std::vector<any> aContainerList={any(aFooArray), any(aBarVector)};
そして、下記のようなイメージで枚挙処理を行いたいのです。
for (auto&& element : aContainerList[index].getRange())
{
std::cout << element.getName() << "\n";
}
たぶん、イテレータにboost:anyテクニックを応用すれば可能と思いますが、コピー時にnewすることになります。イテレータはコピー負荷が軽いことが前提な型っぽい(I/Fの多くが参照渡しではなく値渡し)ので、これは避けたいです。
以前、こちらで質問し、その過程で思いついたものの改造版をQiitaへ投稿しました。
そのRangeクラス内にboost::anyテクニックを使って、異なるコンテナ用イテレータを保持することで対応できそうな気がしているのですが、その前に何か良いアイデアがあればと思い、質問させて頂いています。
自作してみました
yohhoyさんに教えていただいたboostの実装を参考にしつつ、自作してみました。
イテレータではなく範囲ベースfor専用のレンジをboost:any的に保持してます。
範囲ベースfor専用のレンジはQiitaへ投稿したrange-based forをより使いやすくするを改造しました。
後、下記ソースではContainerHolderもboost::anyのようにしてますが、こちらは勿体無い気がしてます。コンテナへの参照をunique_ptr<>で保持してます。わざわざヒープ領域に「参照変数領域」を確保していることになるので、なんか違う気がするのですが、良い方法を思いつきませんでした。
poly_range.h
#ifndef POLY_RANGE_H
#define POLY_RANGE_H
#include <memory>
//----------------------------------------------------------------------------
// 範囲ベースforへ渡すリファレンサー(RBForReferencerの特化)
//----------------------------------------------------------------------------
template<class tPolyRangeBase, class tElementBase>
class PolyReferencer
{
tPolyRangeBase& mPolyRange;
public:
PolyReferencer(tPolyRangeBase& iPolyRange) : mPolyRange(iPolyRange)
{ }
tElementBase& operator*()
{
return mPolyRange.mHolder->front();
}
tElementBase const& operator*() const
{
return mPolyRange.mHolder->front();
}
void operator++()
{
mPolyRange.mHolder->drop_front();
}
bool operator!=(PolyReferencer const& iRhs) const
{
if (!mPolyRange.mHolder.get())
return false;
return !mPolyRange.mHolder->empty();
}
};
//----------------------------------------------------------------------------
// 枚挙用Range
//----------------------------------------------------------------------------
template<class tElementBase>
class PolyRange
{
// ---<<< 基底クラス >>>---
struct HolderBase
{
virtual tElementBase & front() = 0;
virtual tElementBase const& front() const = 0;
virtual void drop_front() = 0;
virtual bool empty() const = 0;
// これがないとmsvc 2015の「反復子のデバッグのサポート」用の
// イテレータのデストラクタが呼ばれないため、異常動作する。
virtual ~HolderBase() { }
};
// ---<<< 実クラス >>>---
template<typename tIterator>
struct Holder : public HolderBase
{
tIterator mBegin;
tIterator mEnd;
Holder(tIterator&& iBegin, tIterator&& iEnd) :
mBegin(std::forward<tIterator>(iBegin)),
mEnd (std::forward<tIterator>(iEnd))
{ }
~Holder() = default;
tElementBase& front()
{
return *mBegin;
}
tElementBase const& front() const
{
return *mBegin;
}
void drop_front()
{
++mBegin;
}
bool empty() const
{
return mBegin == mEnd;
}
};
// ---<<< 各種設定 >>>---
template<class, class> friend class PolyReferencer;
typedef PolyReferencer<PolyRange, tElementBase> PolyReferencer;
// ---<<< 記録領域 >>>---
std::unique_ptr<HolderBase> mHolder;
// ---<<< コンストラクタ >>>---
public:
PolyRange() { }
template<typename tIterator>
PolyRange(tIterator&& iBegin, tIterator&& iEnd) :
mHolder
(
new Holder<tIterator>
(
std::forward<tIterator>(iBegin),
std::forward<tIterator>(iEnd)
)
)
{ }
PolyReferencer begin() {return PolyReferencer(*this);}
PolyReferencer end() {return PolyReferencer(*this);}
};
//----------------------------------------------------------------------------
// コンテナのホルダ
// 保持しているコンテナの範囲ベースfor専用Rangeを獲得する機能を持つ
//----------------------------------------------------------------------------
template<class tElementBase>
class ContainerHolder
{
// ---<<< 基底クラス >>>---
struct HolderBase
{
virtual ~HolderBase() { }
virtual PolyRange<tElementBase> getRange() = 0;
};
// ---<<< 実クラス >>>---
template<class tContainer>
struct Holder : public HolderBase
{
tContainer& mContainer;
Holder(tContainer& iContainer) : mContainer(iContainer)
{ }
~Holder() = default;
PolyRange<tElementBase> getRange()
{
return PolyRange<tElementBase>(std::begin(mContainer), std::end(mContainer));
}
};
// ---<<< 記録領域 >>>---
std::unique_ptr<HolderBase> mHolder;
// ---<<< コンストラクタ >>>---
public:
template<typename tContainer>
ContainerHolder(tContainer& iContainer) :
mHolder(new Holder<tContainer>(iContainer))
{ }
PolyRange<tElementBase> getRange()
{
return mHolder->getRange();
}
};
#endif // POLY_RANGE_H
以下、サンプル・ソースです。
main.cpp
#include <iostream>
#include <vector>
#include <string>
#include "poly_range.h"
// 要素の型
struct ElementBase
{
virtual ~ElementBase() { }
virtual char const* getName() = 0;
};
struct Foo : public ElementBase
{
std::string const mName;
char const* getName()
{
return mName.c_str();
}
Foo(std::string const& iName) : mName(iName) { }
};
struct Bar : public ElementBase
{
char const* mName;
char const* getName()
{
return mName;
}
Bar(char const* iName) : mName(iName) { }
};
// コンテナ
Foo gFooArray[]={ {"Foo0"}, {"Foo1"}, {"Foo2"} };
std::vector<Bar> gBarVector={ "Bar0", "Bar1", "Bar2" };
// コンテナのリスト
typedef ContainerHolder<ElementBase> Container;
std::vector<Container> gContainerList;
int main()
{
std::size_t aFooArrayIndex=gContainerList.size();
gContainerList.emplace_back(Container(gFooArray));
std::size_t aBarVectorIndex=gContainerList.size();
gContainerList.emplace_back(Container(gBarVector));
std::cout << "--- FooArray ---\n";
for (auto&& element : gContainerList[aFooArrayIndex].getRange())
{
std::cout << element.getName() << "\n";
}
std::cout << "\n";
std::cout << "--- BarVector ---\n";
for (auto&& element : gContainerList[aBarVectorIndex].getRange())
{
std::cout << element.getName() << "\n";
}
std::cout << "\n";
return 0;
}
【補足】
gContainerList
への値設定時、初期化リストを使おうとしたのでずか、ContainerHolderのコピー・コンストラクタが呼び出され、unique_ptrを使っているので暗黙的にdeleteされており、コンパイル・エラーでした。
push_backやemplace_backではムーブだけでよいので、原因はよく分かりませんでした。
もし、分かる方がいらっしゃいましたら、何故初期化リストではコピー・コンストラクタが必要になるのかご教授頂けると幸いです。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+2
要件をイマイチ理解していないので外しているかもしれませんが、Boost.Rangeでは any_range が提供されます。その名前通り"イテレータにboost:anyテクニックを応用"したものですね。
公式ドキュメントにも記載がありますが、Type Erasureを使っているため仮想関数呼び出しコストは存在します。
追記: 追加された疑問に回答します。
何故初期化リストではコピー・コンストラクタが必要になるのか
初期化リストを受け取る側のstd::initializer_list<E>
クラステンプレートでは、const修飾された要素型にしかアクセスできないため、C++14現在では要素型E
にはコピー操作を要求します。
ただやはり不便ですし必ずしも必須要件でもないため、N4166: Movable initializer lists で改善提案がされているようです。最新の提案文書(v2)は P0065R0 です。提案文書によれば、C++11(旧C++0x)時点では時間切れでムーブセマンティクスに対応できなかったとのことで、将来的には何らかのサポートがあるかもしれませんね。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.35%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
yohhoy
2016/05/08 15:23
elementの型には何を期待するのでしょう?FooとBarには何の相関もないため、動的な(実行時)ポリモーフィズムの場合、何の操作も期待できない気がします。静的(コンパイル時の)ポリモーフィズムが前提ですか?
Chironian
2016/05/08 17:34
ああっ、確かにそうです。見落としてました。FooとBarは共通の基底クラスを持つ必要がありますね。質問文を修正します。