C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[13]: クラスの継承とイテレータ


(過去ログ 59 を表示中)

[トピック内 15 記事 (1 - 15 表示)]  << 0 >>

■33798 / inTopicNo.1)  クラスの継承とイテレータ
  
□投稿者/ びーしむ (1回)-(2009/03/10(Tue) 11:26:09)

分類:[C/C++] 

びーしむです。

あるクラス Data がありまして、データは派生クラスで定義しようと思いました。
これらの派生クラスは vector や list などを使ってデータを保持しています。
派生クラスのポインタを受け取れればデータにアクセスできるのですが、
親の Data クラスのポインタしか受け取れないとき、これらのデータにアクセスする方法はないでしょうか?

std::vector<int>::iterator itV;
std::list<int>::iterator   itL;
hoge<int>::iterator it1 = itV;
hoge<int>::iterator it2 = itL;
こんなことができれば、親のクラスに 
virtual hoge<int>::iterator begin()
virtual hoge<int>::iterator end()
を定義できるのに思ったのですが。。。

よろしくお願いします。

以下、ソース
#include <numeric>
#include <vector>
#include <list>

class Data {};

class DataVector : public Data {
public:
	std::vector<int> m_Data;
}

class DataList : public Data {
public:
	std::list<int> m_Data;
}

main()
{
	DataVector 	data1;
	DataList	data2;
	int sum1 = std::accumulate(data1.m_Data.begin(), data1.m_Data.end(), 0);
	int sum2 = std::accumulate(data2.m_Data.begin(), data2.m_Data.end(), 0);

	Data *p1 = &data1;
	Data *p2 = &data2;
	int sum3 = std::accumulate(p1->begin(), p1->end(), 0);	//これを実現する方法はあるかな?
	int sum4 = std::accumulate(p2->begin(), p2->end(), 0);	//これを実現する方法はあるかな?
}


引用返信 編集キー/
■33800 / inTopicNo.2)  Re[1]: クラスの継承とイテレータ
□投稿者/ επιστημη (1812回)-(2009/03/10(Tue) 11:43:19)
επιστημη さんの Web サイト
2009/03/10(Tue) 11:57:56 編集(投稿者)
> 親の Data クラスのポインタしか受け取れないとき、これらのデータにアクセスする方法はないでしょうか?

たとえできたとしても
 
> int sum3 = std::accumulate(p1->begin(), p1->end(), 0);	//これを実現する方法はあるかな?
> int sum4 = std::accumulate(p2->begin(), p2->end(), 0);	//これを実現する方法はあるかな?

戻り値の型が int であることをどうやって知る?
std::vector<std::string> m_Data;
std::list<double> m_Data;
かもしんないよね?

型はint確定だってんなら、↓こんなお茶の濁しかたなら。

#include <numeric>
#include <vector>
#include <list>

class Data {
public:
  virtual int accumulate() const =0;
};

class DataVector : public Data {
public:
	std::vector<int> m_Data;
   virtual int accumulate() const { return std::accumulate(m_Data.begin(), m_Data.end(), 0); }
}

class DataList : public Data {
public:
	std::list<int> m_Data;
   virtual int accumulate() const { return std::accumulate(m_Data.begin(), m_Data.end(), 0); }
}

main()
{
	DataVector 	data1;
	DataList	data2;

	Data *p1 = &data1;
	Data *p2 = &data2;
	int sum3 = p1->accumulate();
	int sum4 = p2->accumulate();
}

引用返信 編集キー/
■33802 / inTopicNo.3)  Re[2]: クラスの継承とイテレータ
□投稿者/ びーしむ (2回)-(2009/03/10(Tue) 13:02:03)
επιστημη さん返信ありがとうございます。

> 戻り値の型が int であることをどうやって知る?
> 型はint確定だってんなら
データの持ち方が違うだけで、型については固定です。


>こんなお茶の濁しかたなら。
accumulate のような処理を複数個用意したいとき(またはメンバ関数にしたくないときなど)は、
ヘルパ関数を用意して

int AccumulateHelper(Data *p)
{
	DataVector *pV = dynamic_cast<DataVector*>(p);
	if(pV){return std::accumulate(pV->m_Data.begin(), pV->m_Data.end(), 0);}

	DataList *pL = dynamic_cast<DataList*>(p);
	if(pL){return std::accumulate(pL->m_Data.begin(), pL->m_Data.end(), 0);}

	return 0;
}

int AverageHelper(Data *p)
{
	DataVector *pV = dynamic_cast<DataVector*>(p);
	if(pV){return AverageImpl(pV->m_Data.begin(), pV->m_Data.end());}

	DataList *pL = dynamic_cast<DataList*>(p);
	if(pL){return AverageImpl(pL->m_Data.begin(), pL->m_Data.end());}

	return 0;
}

とやればできそうです。
処理の実装は、template を使えば良いのかな。

引用返信 編集キー/
■33803 / inTopicNo.4)  Re[3]: クラスの継承とイテレータ
□投稿者/ 774RR (321回)-(2009/03/10(Tue) 13:18:00)
えぇーそれは禁じ手だにょ。俺なら絶対却下。
俺の後輩君がそんなコード書いてたら全捨てさせるレベルの話。

何をどう使うか、どう使うべきか、は設計方針次第だったりするので一口にいえない。
でも、提示のコードを使うくらいなら要件を整理して設計しなおすべきだと思う。

・ class Data は、その派生クラスにてコレクションを持つことを知っているべきか否か
・ class Data は、派生クラスにて accumulate や avarage 演算があることを知っているべきか否か
その辺から整理するべきで、案件分析なしにとってつけたような関数を作るのは無意味。

引用返信 編集キー/
■33804 / inTopicNo.5)  Re[3]: クラスの継承とイテレータ
□投稿者/ επιστημη (1813回)-(2009/03/10(Tue) 13:23:29)
επιστημη さんの Web サイト
2009/03/10(Tue) 14:06:32 編集(投稿者)

> とやればできそうです。

基底クラスがポリモーフィックなクラスならdynamic_castできるですね。
...けど実際んなことやるかなー...

> class Data は、その派生クラスにてコレクションを持つことを知っているべきか否か

ですねぇ。
Dataの利用者はDataの腹ん中を知らんでも使えるのがベストちゃうんか?
って考えますです。

引用返信 編集キー/
■33808 / inTopicNo.6)  Re[4]: クラスの継承とイテレータ
□投稿者/ びーしむ (3回)-(2009/03/10(Tue) 15:04:13)
> class Data は、その派生クラスにてコレクションを持つことを知っているべきか否か
class Data はデータを保持するのが目的で、そのデータをどう使うかは Data の利用者に任せたい。
データを数式で表せるとき vector や list に展開して保持しなくていいからメモリが節約できると考えた。
例として accumulate や avarage を挙げましたが、他のどのような関数が必要になるかはわからない。

>Dataの利用者はDataの腹ん中を知らんでも使えるのがベストちゃうんか? 
class Data が iterator(派生クラスのイテレータをラッパーするようなクラスでもよい) の
begin, end を返せればできるなと思ったのですがその手法が良くないみたいですね。

他に考えた方法は、
1.処理時間を考えないで、std::vector<int> のコピーでデータを取得する関数を用意する
class Data {
public:
    virtual const std::vector<int> Get()const=0;
};

2.データは class Data が保持し、派生クラスは Update 関数を実装して m_Data を更新する。
class Data {
public:
    const std::vector<int>& Get()const{return m_Data;}
    virtual void Update(){}

protected:
    std::vector<int> m_Data;
};


なかなか難しいですね。。。


引用返信 編集キー/
■33810 / inTopicNo.7)  Re[5]: クラスの継承とイテレータ
□投稿者/ επιστημη (1814回)-(2009/03/10(Tue) 15:17:36)
επιστημη さんの Web サイト
2009/03/10(Tue) 15:34:00 編集(投稿者)

> class Data はデータを保持するのが目的で、そのデータをどう使うかは Data の利用者に任せたい。

データをvectorで保持したいか? それともlistか? は利用者が選択するのかしら。
もしそうなら、template-template parameter を使って

Data<std::vector> とか Data<std::list> なんてな、"コンテナを指定したData"が定義できますが。

そこまで凝らんでも、たとえば

template<typename Container>
class Data {
Container m_Data;
};

Data< std::vector<int> > user_data;

みたいに使っちゃいかんのかしら?

引用返信 編集キー/
■33815 / inTopicNo.8)  Re[6]: クラスの継承とイテレータ
□投稿者/ 774RR (322回)-(2009/03/10(Tue) 16:46:35)
> class Data はデータを保持するのが目的で、そのデータをどう使うかは Data の利用者に任せたい。
> データを数式で表せるとき vector や list に展開して保持しなくていいからメモリが節約できると考えた。
> 例として accumulate や avarage を挙げましたが、他のどのような関数が必要になるかはわからない。

案件が衝突しているわけだ。
・どんなデータの持ち方するかわからないが、データは持ちたい
・どんな演算するかわからないが、演算したい
・データと演算を分離したい
ってことだろう?
不明な持ち方しているデータを、データの持ち方知らないルーチンで演算したい
ってのは無理な話。

まあそういう場合のために特殊化って機能があるので template + 特殊化、で実現してもいいかな。
・Data は一般的に内部に STL コンテナを持つ(持たない Data も作ることができる)
・STL コンテナを持つ Data に対しては自作 Accumurate/Avarage が無条件で適用できる
・内部に STL コンテナを持たない Data を作った場合には
 それ専用の Accumurate/Avarage を特殊化で実装する
ってやり方もある、っちゃあるな。
引用返信 編集キー/
■33820 / inTopicNo.9)  Re[7]: クラスの継承とイテレータ
□投稿者/ びーしむ (4回)-(2009/03/10(Tue) 17:43:28)
επιστημη さん、774RRさん、回答ありがとうございます。

> Data< std::vector<int> > user_data;
> みたいに使っちゃいかんのかしら?
こうですか?
template<typename Container>
class Data {
public:
    Container m_Data;
};

template<typename T>
int AccumulateHelper(Data<T> *p)
{
    return std::accumulate(p->m_Data.begin(), p->m_Data.end(), 0);
}

main()
{
    Data<std::vector<int> > data1;
    Data<std::list<int> >   data2;
    int sum1 = AccumulateHelper(&data1);
    int sum2 = AccumulateHelper(&data2);
}


>不明な持ち方しているデータを、データの持ち方知らないルーチンで演算したい
>ってのは無理な話。
ここを解決するために std::list のような begin(), end() と取得したイテレータ?に ++, -- がある
class Data が定義できればと考えました。
自分の例の場合はデータの型に汎用性は必要ないので
下記のインターフェースをもつ class Data のポインタを受け渡しすればと思ったのですが。
この考え方が間違ってるのかなぁ・・・

class Data {
public:
   class const_iterator {
       int operator * ()const;
       int operator ++ ();
       int operator -- ();
       ...
   };

    virtual const_iterator begin()const;
    virtual const_iterator end()const;
};


template + 特殊化ついては理解できていないので勉強しておきます。


引用返信 編集キー/
■33821 / inTopicNo.10)  Re[8]: クラスの継承とイテレータ
□投稿者/ επιστημη (1815回)-(2009/03/10(Tue) 17:46:14)
επιστημη さんの Web サイト
>>Data< std::vector<int>>user_data;
>>みたいに使っちゃいかんのかしら?
> こうですか?
> ...

うん、たとえばそんな。
これでなんか不都合があるのでしょうか。

引用返信 編集キー/
■33825 / inTopicNo.11)  Re[9]: クラスの継承とイテレータ
□投稿者/ 774RR (323回)-(2009/03/10(Tue) 18:37:14)
そもそも container に対して virtual を使うのが相性悪い方法なので
・ Data は template としておく
・ Data に対する演算も template としておく (Write Once はここで達成可能)
・ Data の任意のインスタンス化に対して専用ルーチンが作られるコードサイズの肥大には目をつぶる
・その代わり virtual を使わずにすみ実行速度は向上する
ってことになるわけだよ

そうやって実装されたのが No33820 の前半分のコードってことになる。
これなら満点あげていい

No33820 の後ろ半分のコードが実装できたとして、それを使って何か目的の演算ルーチンを呼ぶとする。
こちらの場合、生成される目標の演算に対応するバイナリコードは1つですむんだろうけど
その演算中に行われるすべての操作 begin() ++ -- 等々の度に virtual call がなされるわけで
実行速度的には見る影もない・・・ってころになりかねない。
なら最初から template ベースで演算ルーチンまで組んぢゃえ、というのことさ。
引用返信 編集キー/
■33834 / inTopicNo.12)  Re[10]: クラスの継承とイテレータ
□投稿者/ επιστημη (1816回)-(2009/03/10(Tue) 22:15:20)
επιστημη さんの Web サイト
2009/03/10(Tue) 22:15:37 編集(投稿者)
メンバ関数まるっと横流しして↓こんなんでもいんじゃねぇかと。

#include <vector>
#include <list>
#include <iostream>
#include <numeric>

template<typename Container>
class Data {
    Container m_Data;
public:
  typedef typename Container::iterator iterator;
  typedef typename Container::const_iterator const_iterator;
  typedef typename Container::value_type value_type;
  iterator begin() { return m_Data.begin(); }
  const_iterator begin() const { return m_Data.begin(); }
  iterator end() { return m_Data.end(); }
  const_iterator end() const { return m_Data.end(); }
  void push_back(const value_type& x) { m_Data.push_back(x); }
};

int main() {
    Data<std::vector<int> > data;
//  Data<std::list<int> > data;
    data.push_back(1);
    data.push_back(2);
    data.push_back(3);
    
    int sum = std::accumulate(data.begin(), data.end(), 0);
    std::cout << sum << std::endl;
}

引用返信 編集キー/
■33845 / inTopicNo.13)  Re[11]: クラスの継承とイテレータ
□投稿者/ びーしむ (5回)-(2009/03/11(Wed) 09:56:24)
> なら最初から template ベースで演算ルーチンまで組んぢゃえ、というのことさ。
こちらの方法で再検討してみます。

>メンバ関数まるっと横流しして↓こんなんでもいんじゃねぇかと。
この方法が良いです。

επιστημη さんが
>基底クラスがポリモーフィックなクラスならdynamic_castできるですね。
>...けど実際んなことやるかなー...
と書いたケースですが、自分の場合は、
Data<std::vector<int> > と Data<std::list<int> > のポインタを
vector< Data??? * > 等で管理したい場合でした。


vector< Data??? * > 等で管理する方が重要なので、再検討すると
最初のほうに戻ってしましますが、以下の方法になってしまいました。
vector< Data??? * > に入れるポインタは、実行時にきまるので template は使えない。
そうすると演算用のデータの型は1つに決めて(たとえば vector<int> のみ) No33808 のように
データを取得する関数を virtual で実装する。(データ取得時の処理時間を犠牲にする)
データの読み出し関数は取得する範囲を指定できるようにして、利用者が必要な範囲を指定できるようにする。
virtual int Get(vector<int> *p); //全データ取得
virtual int Get(vector<int> *p, int begin, int end); //範囲指定(範囲指定の型は適当)

まとめると、はじめは class Data をコンテナのように使おうと思っていましたが
1.データの保持とデータを読み出す機能だけにする。
2.読み出したデータの型は決まっているので、演算ルーチンを後から作成できる。
3.読み出したデータのポインタを vector 等で使用できる。

どうですか?
引用返信 編集キー/
■33846 / inTopicNo.14)  Re[12]: クラスの継承とイテレータ
□投稿者/ επιστημη (1817回)-(2009/03/11(Wed) 10:36:03)
επιστημη さんの Web サイト
2009/03/11(Wed) 10:36:26 編集(投稿者)
> Data<std::vector<int>>と Data<std::list<int>>のポインタを
> vector< Data??? * > 等で管理したい場合でした。

↓こんな雰囲気かのぉ...

#include <iostream>

#include <vector>
#include <list>
#include <iostream>
#include <numeric>
#include <algorithm>

template<typename T>
class AbstractData {
public:
  virtual int size() const = 0;
  virtual T* copy_to(T* out) const =0;
};

template<typename T,  template <typename U, class A=std::allocator<U> > class Container>
class Data : public AbstractData<T> {
  typedef typename Container<T> container;
  container data_;
public:
  typedef typename container::iterator iterator;
  typedef typename container::const_iterator const_iterator;
  typedef typename container::value_type value_type;
  iterator begin() { return data_.begin(); }
  const_iterator begin() const { return data_.begin(); }
  iterator end() { return data_.end(); }
  const_iterator end() const { return data_.end(); }
  void push_back(const value_type& x) { data_.push_back(x); }
  
  virtual int size() const { return data_.size(); }
  virtual T* copy_to(T* out) const { return std::copy(begin(), end(), out); }
};

int main() {
    std::vector<AbstractData<int>*> vad;
    
    Data<int,std::vector> vdata;
    vdata.push_back(1);
    vdata.push_back(2);
    vdata.push_back(3);
    vad.push_back(&vdata);

    Data<int,std::list>   ldata;
    ldata.push_back(4);
    ldata.push_back(5);
    ldata.push_back(6);
    ldata.push_back(7);
    vad.push_back(&ldata);
    
    for ( int i = 0; i < vad.size(); ++i ) {
      AbstractData<int>* pdata = vad.at(i);
      int n = pdata->size();
      int* first = new int[n];
      int* last = pdata->copy_to(first);
      int sum = std::accumulate(first, last, 0);
      std::cout << "sum-" << i << '\t' << sum << std::endl;
      delete[] first;
    }
}

引用返信 編集キー/
■33850 / inTopicNo.15)  Re[13]: クラスの継承とイテレータ
□投稿者/ びーしむ (6回)-(2009/03/11(Wed) 14:08:07)
考え方の指摘や多くのサンプルコードをいただき感謝です。
今回は、No33845 , No33846 の方法を使おうと思います。

επιστημηさん、774RRさん、
ありがとうございました。
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -