継承とアップキャストとstd::listについての質問です

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Eeel

継承とアップキャストとstd::listについての質問です

#1

投稿記事 by Eeel » 7年前

目的や躓いている点を上手く説明できず、冗長な上に不完全な質問になってしまいましたが、
どうかよろしくお願いします。

■やりたい事
・基底クラスCBaseを継承した、派生クラス複数種類 CSub1~CSubNを、
 それぞれの型のポインタの、オブジェクトの動的な生成に応じてサイズの変わる可変長の配列のような物で管理したい。(下記ソースではstd::listを使っています)
・その可変長の配列のような物のポインタを引数に取り、CBaseが持つ、もしくはオーバーライドされたメンバ関数を呼び出す関数を作りたい。

■避けたい事
・派生クラス複数種類 CSub1~CSubNを,CBase*型で管理する事。
・可変長でない方法(例えば普通の配列 CSub1* s1List[MAX_ARRAY]; 等)で管理する事。
・関数を呼び出す前に、引数として扱う為のCBase*型のリストを用意する事。

コード:

#include <iostream>
#include <list>

using namespace std;

// 基底クラス
class CBase
{
public:
	virtual void m_Func(){
		cout << "CBase" << endl;
	}
};

// 派生クラス
class CSub1 : public CBase
{
public:
	void m_Func(){
		cout << "CSub1" << endl;
	}
};

// CBaseとは無関係のクラス
class COther
{
public:
	void Func(){
		return;
	}
};

// クラスのポインタを引数にする関数
void func1(CBase* pCBase)
{
	pCBase->m_Func();
}

// クラスのポインタのlistのポインタを引数にする関数
void func2(list<CBase*>* pCBaseList)
{
	(*pCBaseList->begin())->m_Func();
}

// listをひとまずvoid*で受け取る関数
void func3(void* list)
{
	(*((std::list<CBase*>*)list)->begin())->m_Func();	// キャストしてメンバ関数を呼び出す
}

int main(){
	// オブジェクトとポインタ変数の準備
	CBase* oBase = new CBase;
	CSub1* oSub1 = new CSub1;

	// listの準備
	list<CBase*> baList;
	list<CSub1*> s1List;

	baList.push_back( oBase );
	s1List.push_back( oSub1 );

	// クラスのポインタを引数にする関数を呼び出す
	func1( oBase );
	func1( oSub1 );			// アップキャストが行われる
	cout << endl;

	// クラスのポインタのlistのポインタを引数にする関数を呼び出す
	func2( &baList );
//	func2( &s1List );		// この行は引数の型が合わずにエラー
	cout << endl;

	// listをひとまずvoid*で受け取る関数を呼び出す
	func3( &baList );
	func3( &s1List );		// 成功
	cout << endl;

	// でも……
	// CBaseを継承しないクラスのリストをfunc3()に渡したりすると……
	COther* oOther = new COther;
	std::list<COther*> otList;
	otList.push_back( oOther );
//	func3( &otList );			// 実行時にアクセス違反

	return 0;
}
////////実行結果////////
CBase
CSub1

CBase

CBase
CSub1

////////////////////////

■問題点

コード:

//	func2( &s1List );		// この行は引数の型が合わずにエラー

コード:

//	func3( &otList );			// 実行時にアクセス違反
です。
前者は引数として渡せないので、期待通りの動作になりません。
後者は一応は期待通りの動作はするのですが、ミスがあった場合にビルド時には発見できないバグが潜んでしまいます。

説明の順序が逆になってしまったかもしれませんが、
クラスCBaseは、「funcNが引数に取れるクラス」の基底クラスにしたいのです。
最初はfunc1()のようにアップキャストが行われ、上手くいきそうでした。
しかし、ポインタをlistで管理しようとしたとたん、上手くいかなくなりました。
いずれは、funcN(char str[]);やfuncN(int* value);などでオーバーロードし、何種類かの変数にも対応させたいと思っています。


■どうしたら解決できるか
・func1()とfunc2()を合わせたような具合に、list<CSub1*>*型をlist<CBase*>*型にキャストできれば解決。
・func3()で実行時にアクセス違反が起こるようなキャストを行った場合、ビルド時にエラーが発生するようにできれば解決。
どちらか一方でも解決ですが、可能なのでしょうか?

その他にも何か別の方法や基本的な事を見落としているかも知れないです。
何か気付いた事がありましたら指摘してください。

アバター
h2so5
副管理人
記事: 2212
登録日時: 9年前
住所: 東京
連絡を取る:

Re: 継承とアップキャストとstd::listについての質問です

#2

投稿記事 by h2so5 » 7年前

テンプレートを使用するのはどうでしょう?

コード:

template <class T>
void func2(list<T*>* pCBaseList)
{
    static_cast<CBase*>(*pCBaseList->begin())->m_Func();
}

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: 継承とアップキャストとstd::listについての質問です

#3

投稿記事 by YuO » 7年前

前提として,クラスBとそれを継承したクラスDがあり,別にクラステンプレートC<T>があるとき,
  • DはBを継承しているので

    コード:

    B * p1 = new D();
    は正しい
  • C<D>はC<B>を継承していないので

    コード:

    C<B> * p2 = new C<D>();
    は正しくない
  • C<D *>はC<B *>を継承していないので

    コード:

    C<B *> * p3 = new C<D *>();
    は正しくない
となっています。
コンテナ要素の継承関係は実体化されたコンテナの継承関係に影響を与えません。

JavaのgenericsのワイルドカードやC#のgenericsにおけるin/outは上記の制限の一部を取り払うものですが,templateにはその機構がありません。
# genericsは実体が1種類だから可能。templateは型ごとに実体が作られる。


個人的には,func2のような形の関数は作らず,呼び出し側で要素を決定してfunc1を呼ぶで十分に思えたりするのですが……。

コード:

template <typename Iterator> void func2ex (Iterator begin, Iterator end)
{
    for (; begin != end; ++begin) (*begin)->m_Func();
}
のような書き方ならありだとは思います。
# std::for_eachとstd::bindとかで処理できそうな……。

dic
記事: 582
登録日時: 9年前
住所: 宮崎県

Re: 継承とアップキャストとstd::listについての質問です

#4

投稿記事 by dic » 7年前

コード:

// クラスのポインタのlistのポインタを引数にする関数
void func2(list<CBase*>* pCBaseList)
{
    (*pCBaseList->begin())->m_Func();
}
void func2(list<CSub1*>* pCBaseList)
{
    (*pCBaseList->begin())->m_Func();
}
上のように オーバーロードを実装する

void fuc3( ... は typeid (RTTI) で実行時に型をチェックし、条件分岐する

あまりやりたくないかもしれませんが、このような方法ではどうでしょうか?

Eeel

Re: 継承とアップキャストとstd::listについての質問です

#5

投稿記事 by Eeel » 7年前

h2so5 さんが書きました:テンプレートを使用するのはどうでしょう?
テンプレートも試してみたのですが、

コード:

void func2(T* pCBaseList)
という形にして上手くいかず諦めていました。
教えて頂いた形なら上手くいきそうです。
ありがとうございました。

YuO さんが書きました:コンテナ要素の継承関係は実体化されたコンテナの継承関係に影響を与えません。
個人的には,func2のような形の関数は作らず,呼び出し側で要素を決定してfunc1を呼ぶで十分に思えたりするのですが……。
func2のようなやり方はそもそも無理だったのですね。
呼び出す時は

コード:

func( &a );
func( &b );
func( &c );
というように、型を気にせず利用できるようなクラスを作りたいと思っていました。
説明不足ですみませんでした。
std::for_eachとstd::bindでのやり方は検討もつかないので今後の課題のひとつにしてみます。
ありがとうございました。

dic さんが書きました:上のように オーバーロードを実装する
void fuc3( ... は typeid (RTTI) で実行時に型をチェックし、条件分岐する
CSubNの種類の数と内容は未定なので、オーバーロードだとCBaseを継承するクラスを作る度にfunc()について書き加えなくてはならなくなってしまい、それはあまり好ましくないような気がするのです。
これも説明不足でした。すみません。
typeidは試してみましたが、func3内では全てvoid*型だという結果になってしまいました。
やり方がよくなかったのかも知れません。
ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る