質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

3242閲覧

C++:コンテナをポリモーフィックに枚挙する方法について

Chironian

総合スコア23272

C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2016/05/06 02:50

編集2016/05/14 05:15

生配列や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ではムーブだけでよいので、原因はよく分かりませんでした。
もし、分かる方がいらっしゃいましたら、何故初期化リストではコピー・コンストラクタが必要になるのかご教授頂けると幸いです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yohhoy

2016/05/08 06:23

elementの型には何を期待するのでしょう?FooとBarには何の相関もないため、動的な(実行時)ポリモーフィズムの場合、何の操作も期待できない気がします。静的(コンパイル時の)ポリモーフィズムが前提ですか?
Chironian

2016/05/08 08:34

ああっ、確かにそうです。見落としてました。FooとBarは共通の基底クラスを持つ必要がありますね。質問文を修正します。
guest

回答1

0

ベストアンサー

要件をイマイチ理解していないので外しているかもしれませんが、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)時点では時間切れでムーブセマンティクスに対応できなかったとのことで、将来的には何らかのサポートがあるかもしれませんね。

投稿2016/05/09 00:36

編集2016/05/15 03:01
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Chironian

2016/05/09 01:40

情報、ありがとうございます。 まず、boost::AnyのようなテクニックのことをType Erasureと呼ぶのですね。やっと理解できました。(以前見た時はまだboost:Anyを理解してなかったので理解できなかったようです。) そして、私がやりたいことはRange自体がType Erasureになっている必要が有ります。 コンテナ要素の型を知らないまま枚挙したいからです。 boost:any_rangeはテンプレート・パラメータにコンテナの要素の型を与える必要が有るため、残念ながら使えなさそうです。 思っていたよりレアな要求のようですので、自作しようと思います。 満足できるものを作れたら、ここにて報告します。
Chironian

2016/05/09 10:51

ちょっと余裕ができたので、boost::any_rangeのソースを追いかけてみました。 > boost:any_rangeはテンプレート・パラメータにコンテナの要素の型を与える必要が有るため、残念ながら使えなさそうです。 と見えたのですが、すいません。どうも違うようです。 コンテナの要素の型を与える必要はありますが、例えば質問に記載したElementBaseを与えてもよさそうです。といいますか、それが本来の使い方っぽいです。 正に「イテレータにboost:anyテクニックを応用すれば可能と思います」を実現しているもののようです。 しかし、もしかすると偏見かも知れませんが、関数呼び出しでバラメータとして与えるとコピーされ、その度にnewが走りそうでちょっと躊躇してます。(戻り値側はRVO等でコピーしないと思いますが、関数呼び出し側だと値渡しなのでコピーしそうですし、std::moveしてもムーブしてくれない筈。)
Chironian

2016/05/15 03:47

yohhoyさん。 追加質問へも回答頂きありがとうございます。 > 初期化リストを受け取る側のstd::initializer_list<E>クラステンプレートでは、const修飾された要素型にしかアクセスできないため、C++14現在では要素型Eにはコピー操作を要求します。 なるほど。const参照で受け取っているから、ムーブできないのが現在の仕様なのですね。 lvalue, rvalueの両方を受け取れるconst参照はstd::forwardに較べて理解しやすいので良く使いますがconstになってしまうのが欠点です。テンプレート+std::forwardを使えば回避できますがこれは中々難しく、実は今回の実装で初めて使うのに成功しました。 レベルは全然違うと思いますが、const参照よりstd::forwardは難しいという意味では似たような話みたいですね。なるほどです。 私の根性ではとても調査できそうにない内容でした。本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問