生配列やvector等の要素を枚挙することは多いと思います。
その延長線上で、異なる型のコンテナの要素をポリモーフィズム的に枚挙したいのですが、最近のC++なら簡単と思っていたら、意外に難しいです。
しかし、実は私がまだ把握できていない標準ライブラリやboost等にそのような機能をサポートしたクラスがありそうな気もしています。
そのようなクラスをご存知の方、もしくは、簡単な方法をご存知の方がいましたら、是非、ご教授頂けないでしょうか?
#####詳細
例えば、下記のようなコンテナaFooArray[]とstd::vector<Bar>があるとします。
C++
1struct ElementBase 2{ 3 virutal ~ElementBase() { } 4 virtual char const* getName() = 0; 5}; 6 7struct Foo : public ElementBase 8{ 9 std::string const mName; 10 char const* getName() 11 { 12 return mName.c_str(); 13 } 14 : 15}; 16Foo aFooArray[]={ ... }; 17 18struct Bar : public ElementBase 19{ 20 char const* mName; 21 char const* getName() 22 { 23 return mName; 24 } 25 : 26}; 27std::vector<Bar> aBarVector={ ... };
boost:any的なテクニックでそれぞれへのポインタを管理用コンテナに入れてます。
イメージは下記です。(実際にビルドしてないので雰囲気だけ。)
C++
1std::vector<any> aContainerList={any(aFooArray), any(aBarVector)};
そして、下記のようなイメージで枚挙処理を行いたいのです。
C++
1for (auto&& element : aContainerList[index].getRange()) 2{ 3 std::cout << element.getName() << "\n"; 4}
たぶん、イテレータに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
C++
1#ifndef POLY_RANGE_H 2#define POLY_RANGE_H 3 4#include <memory> 5 6//---------------------------------------------------------------------------- 7// 範囲ベースforへ渡すリファレンサー(RBForReferencerの特化) 8//---------------------------------------------------------------------------- 9 10template<class tPolyRangeBase, class tElementBase> 11class PolyReferencer 12{ 13 tPolyRangeBase& mPolyRange; 14public: 15 PolyReferencer(tPolyRangeBase& iPolyRange) : mPolyRange(iPolyRange) 16 { } 17 18 tElementBase& operator*() 19 { 20 return mPolyRange.mHolder->front(); 21 } 22 tElementBase const& operator*() const 23 { 24 return mPolyRange.mHolder->front(); 25 } 26 void operator++() 27 { 28 mPolyRange.mHolder->drop_front(); 29 } 30 bool operator!=(PolyReferencer const& iRhs) const 31 { 32 if (!mPolyRange.mHolder.get()) 33 return false; 34 return !mPolyRange.mHolder->empty(); 35 } 36}; 37 38//---------------------------------------------------------------------------- 39// 枚挙用Range 40//---------------------------------------------------------------------------- 41 42template<class tElementBase> 43class PolyRange 44{ 45 46// ---<<< 基底クラス >>>--- 47 48 struct HolderBase 49 { 50 virtual tElementBase & front() = 0; 51 virtual tElementBase const& front() const = 0; 52 virtual void drop_front() = 0; 53 virtual bool empty() const = 0; 54 55 // これがないとmsvc 2015の「反復子のデバッグのサポート」用の 56 // イテレータのデストラクタが呼ばれないため、異常動作する。 57 virtual ~HolderBase() { } 58 }; 59 60// ---<<< 実クラス >>>--- 61 62 template<typename tIterator> 63 struct Holder : public HolderBase 64 { 65 tIterator mBegin; 66 tIterator mEnd; 67 68 Holder(tIterator&& iBegin, tIterator&& iEnd) : 69 mBegin(std::forward<tIterator>(iBegin)), 70 mEnd (std::forward<tIterator>(iEnd)) 71 { } 72 ~Holder() = default; 73 74 tElementBase& front() 75 { 76 return *mBegin; 77 } 78 tElementBase const& front() const 79 { 80 return *mBegin; 81 } 82 void drop_front() 83 { 84 ++mBegin; 85 } 86 bool empty() const 87 { 88 return mBegin == mEnd; 89 } 90 }; 91 92// ---<<< 各種設定 >>>--- 93 94 template<class, class> friend class PolyReferencer; 95 typedef PolyReferencer<PolyRange, tElementBase> PolyReferencer; 96 97// ---<<< 記録領域 >>>--- 98 99 std::unique_ptr<HolderBase> mHolder; 100 101// ---<<< コンストラクタ >>>--- 102 103public: 104 PolyRange() { } 105 106 template<typename tIterator> 107 PolyRange(tIterator&& iBegin, tIterator&& iEnd) : 108 mHolder 109 ( 110 new Holder<tIterator> 111 ( 112 std::forward<tIterator>(iBegin), 113 std::forward<tIterator>(iEnd) 114 ) 115 ) 116 { } 117 118 PolyReferencer begin() {return PolyReferencer(*this);} 119 PolyReferencer end() {return PolyReferencer(*this);} 120}; 121 122//---------------------------------------------------------------------------- 123// コンテナのホルダ 124// 保持しているコンテナの範囲ベースfor専用Rangeを獲得する機能を持つ 125//---------------------------------------------------------------------------- 126 127template<class tElementBase> 128class ContainerHolder 129{ 130 131// ---<<< 基底クラス >>>--- 132 133 struct HolderBase 134 { 135 virtual ~HolderBase() { } 136 virtual PolyRange<tElementBase> getRange() = 0; 137 }; 138 139// ---<<< 実クラス >>>--- 140 141 template<class tContainer> 142 struct Holder : public HolderBase 143 { 144 tContainer& mContainer; 145 146 Holder(tContainer& iContainer) : mContainer(iContainer) 147 { } 148 ~Holder() = default; 149 150 PolyRange<tElementBase> getRange() 151 { 152 return PolyRange<tElementBase>(std::begin(mContainer), std::end(mContainer)); 153 } 154 }; 155 156// ---<<< 記録領域 >>>--- 157 158 std::unique_ptr<HolderBase> mHolder; 159 160// ---<<< コンストラクタ >>>--- 161 162public: 163 template<typename tContainer> 164 ContainerHolder(tContainer& iContainer) : 165 mHolder(new Holder<tContainer>(iContainer)) 166 { } 167 168 PolyRange<tElementBase> getRange() 169 { 170 return mHolder->getRange(); 171 } 172}; 173 174#endif // POLY_RANGE_H
以下、サンプル・ソースです。
main.cpp
C++
1#include <iostream> 2#include <vector> 3#include <string> 4#include "poly_range.h" 5 6// 要素の型 7struct ElementBase 8{ 9 virtual ~ElementBase() { } 10 virtual char const* getName() = 0; 11}; 12 13struct Foo : public ElementBase 14{ 15 std::string const mName; 16 char const* getName() 17 { 18 return mName.c_str(); 19 } 20 21 Foo(std::string const& iName) : mName(iName) { } 22}; 23 24struct Bar : public ElementBase 25{ 26 char const* mName; 27 char const* getName() 28 { 29 return mName; 30 } 31 32 Bar(char const* iName) : mName(iName) { } 33}; 34 35// コンテナ 36Foo gFooArray[]={ {"Foo0"}, {"Foo1"}, {"Foo2"} }; 37std::vector<Bar> gBarVector={ "Bar0", "Bar1", "Bar2" }; 38 39// コンテナのリスト 40typedef ContainerHolder<ElementBase> Container; 41std::vector<Container> gContainerList; 42 43int main() 44{ 45 std::size_t aFooArrayIndex=gContainerList.size(); 46 gContainerList.emplace_back(Container(gFooArray)); 47 48 std::size_t aBarVectorIndex=gContainerList.size(); 49 gContainerList.emplace_back(Container(gBarVector)); 50 51 std::cout << "--- FooArray ---\n"; 52 for (auto&& element : gContainerList[aFooArrayIndex].getRange()) 53 { 54 std::cout << element.getName() << "\n"; 55 } 56 std::cout << "\n"; 57 58 std::cout << "--- BarVector ---\n"; 59 for (auto&& element : gContainerList[aBarVectorIndex].getRange()) 60 { 61 std::cout << element.getName() << "\n"; 62 } 63 std::cout << "\n"; 64 65 return 0; 66}
【補足】
gContainerList
への値設定時、初期化リストを使おうとしたのでずか、ContainerHolderのコピー・コンストラクタが呼び出され、unique_ptrを使っているので暗黙的にdeleteされており、コンパイル・エラーでした。
push_backやemplace_backではムーブだけでよいので、原因はよく分かりませんでした。
もし、分かる方がいらっしゃいましたら、何故初期化リストではコピー・コンストラクタが必要になるのかご教授頂けると幸いです。
回答1件
あなたの回答
tips
プレビュー