純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
kddoi
記事: 6
登録日時: 9年前

純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#1

投稿記事 by kddoi » 9年前

度々お世話になっております。表題の件で困っており、色々と調べたのですが解決策には
至っておりません。もし何かヒントのようなものを頂ければ幸いです。

基本クラスにて純粋仮想関数を定義し、それを派生クラスでオーバーライドしております。
基本クラスのポインタを使って派生クラスのオブジェクトを保持し、複数ある派生クラスの動作を
基本クラスの関数で統一的に呼び出すために使っております。
今、派生クラスの方で定義をする関数が、内容はほとんど同じでデータ型のみが異なり、
templateが使えれば非常に楽ができます。ところが、色々と試したのですが、現状だと
Windows 7 x64上のg++ (tdm64-2) 4.8.1でコンパイルを通すことができません。現状では
マクロを使って多少楽をして全ての定義を書く、というくらいしか対処方法が思い付きません。
仮想関数にはtemplateは使えないとの記述はあったので、こちらは宣言のみなので仕方なく
べたに全てのパターンを書いたのですが、せめて派生クラスの実際の定義で無為に長い定義を
するのを避ける方法はありませんでしょうか?

まとめますと、基本クラスの純粋仮想関数を派生クラスにてオーバーライドする際、
データ型のみが異なる関数をできるだけシンプルに書く方法を探しております。
よろしくお願いいたします。参考に、単純化したコードを以下に付けさせていただきます。

コード:

#include <iostream>
using namespace std;

class base
{
public:
    virtual void ConvertData(float dst, double src)=0;
    virtual void ConvertData(double dst, float src)=0;
};

class derived: public base
{
public:
    derived(void){};
    template <typename T1, typename T2> void ConvertData(T1 dst, T2 src){dst=src;};
    //void ConvertData(float dst, double src){dst=src;};
    //void ConvertData(double dst, float src){dst=src;};
};

template void derived::ConvertData(float dst, double src);
template void derived::ConvertData(double dst, float src);

int main()
{
    derived d=derived();
    cout << "main" << endl;
}

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

Re: 純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#2

投稿記事 by h2so5 » 9年前

テンプレートと仮想関数はC++の仕様上両立できないので、どちらかをあきらめることになります。
派生クラスの種類が少ないのであれば仮想関数をやめて手動でディスパッチする手もあります。

kddoi
記事: 6
登録日時: 9年前

Re: 純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#3

投稿記事 by kddoi » 9年前

h2so5 さんが書きました:テンプレートと仮想関数はC++の仕様上両立できないので、どちらかをあきらめることになります。
派生クラスの種類が少ないのであれば仮想関数をやめて手動でディスパッチする手もあります。
やはり、仮想関数とテンプレートは両立できないのですね。仕様書を隅々まで読んで把握できるレベルに逹っして
おりませんので、正しいコーディングを知らないために冗長な書き方になっているのではなく仕様上の限界と分かり、
安心いたしました。ありがとうございます。同じような処理を一部だけ変えて反復して記述していると、変更が生じた際の
書き換えでバグの原因となりますので、できるだけ簡略な書き方にしたいと思います。それから、手動でディスパッチに
ついて検討しようとしたのですが、経験不足のためイメージが掴めませんでした。検索をしてもそれらしいものが
どれか判断ができません。もしお手数でなければ、何かサンプルのようなウェブサイトを教えていただけませんでしょうか?

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

Re: 純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#4

投稿記事 by h2so5 » 9年前

手動でディスパッチすると呼び出し側が冗長になります。
あんまりおすすめする方法ではないですが。

コード:

#include <iostream>
using namespace std;
 
class base
{
public:
	virtual ~base() {}
};
 
class derived1: public base
{
public:
    template <typename T1> 
    T1 ConvertData(T1 val){
    	return val * 2;
    };
};

class derived2: public base
{
public:
    template <typename T1> 
    T1 ConvertData(T1 val){
    	return val * 3;
    };
};
 
int main()
{
	derived1 d1;
	derived2 d2;
	base* array[2] = {&d1, &d2};
	
	for (int i = 0; i < 2; ++i) {
		if (derived1 *d = dynamic_cast<derived1*>(array[i])) {
			cout << d->ConvertData(1.0f) << endl;
		}
		else if (derived2 *d = dynamic_cast<derived2*>(array[i])) {
			cout << d->ConvertData(1.0f) << endl;
		}
	}
}

アバター
usao
記事: 1887
登録日時: 11年前

Re: 純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#5

投稿記事 by usao » 9年前

こんな書き方になるのが嫌だという話でしょうか?

コード:

class base
{
public:
    //ここの時点で既にたくさん書くのが嫌という話?
    virtual void ConvertData(float dst, double src)=0;
    virtual void ConvertData(double dst, float src)=0;
};
 
class derived: public base
{
private:
    //処理内容はtemplateにできるとしても
    template <typename T1, typename T2> void Cvt(T1 dst, T2 src){  dst=src;  }
public:
    //ここは基底と同じようにたくさん書く必要がある
    virtual void ConvertData(float dst, double src) override {  Cvt<float,double>(dst,src);  }
    virtual void ConvertData(double dst, float src) override {  Cvt<double,float>(dst,src);  }
};

kddoi
記事: 6
登録日時: 9年前

Re: 純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#6

投稿記事 by kddoi » 9年前

usao様、h2so5様

ありがとうございます。大変勉強になります。

dynamic_castを使う方法もあったんですね。typeidなどによって実行時情報を元に処理を
分けることも考えたのですが、今実装しようとしているメンバ関数以外にも既に様々な関数を
純粋仮想関数として実装が終わっております。今実装しようとしている関数のみ、
dynamic_castの結果がNULLかどうかの分岐が必要となってしまうので、他のコードとの
整合性の観点で今回はこちらの実装は避けようと思いますが、こういう方法も存在することは
頭の片隅に留めておき、次回以降に生かしたいと思います。

基底クラスの方に、たくさん書かなくてはならないのは仕方ないと思っております。昨日の
段階ですと、そもそもtemplateを使おうとするとコンパイルが通らない状態で、諦めて
マクロで関数の宣言のみならず中身の定義までをマクロで展開して作成してしまおうか、
と考えていたのですが、やはり使えるのであればC++のtemplateを使ってみたいと
思っておりました。なので今回は、基底クラスと派生クラスのpublicの方はマクロを
上手に使ってメインテナンス性を上げ、処理内容は派生クラスの方に書くという方針で
行こうと思います。

プログラミングはほとんど専門の教育を受けておらず趣味レベルでしたが、この度、
自分の専門の分野でツールを作って公開しようと思っております。そのため、趣味から
突然、大規模なプログラムを自力で作ることになり、教科書を片手に必死でコーディングを
しているような状態です。また色々と調べてみても分からないことがあれば質問させて
いただくと思いますが、何卒よろしくお願いいたします。

zeek

Re: 純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#7

投稿記事 by zeek » 9年前

解決済みですけど、私ならこう書くかな。

コード:

class base
{
public:
    virtual ~base() {}
    template <typename T1, typename T2> void ConvertData(T1 dst, T2 src){  Cvt(dst, src);  }
protected:
    virtual void Cvt(float dst, double src)=0;
    virtual void Cvt(double dst, float src)=0;
};
 
class derived: public base
{
protected:
    virtual void Cvt(float dst, double src) {  cvt(dst, src);  }
    virtual void Cvt(double dst, float src) {  cvt(dst, src);  }
private:
    template <typename T1, typename T2> void cvt(T1 dst, T2 src){  dst = static_cast<T1>(src);  }
};
また、お勧めしないけど dynamic_cast 使うならこんな感じかな。

コード:

class base {
public:
    virtual ~base() {}
    template <typename T1, typename T2> void ConvertData(T1 dst, T2 src) {
        get_type()(this, dst, src);
    }
    template <typename T1, typename T2> void cvt_impl(T1 dst, T2 src) {}  // dummy func
    template <typename Derived, typename Base = base>
    class ctype : public Base::reso_type {
    public:
        virtual ~ctype() {}
        virtual void operator()(base *p, float dst, double src) const {
            return dynamic_cast<Derived *>(p)->cvt_impl(dst, src);
        }
        virtual void operator()(base *p, double dst, float src) const {
            return dynamic_cast<Derived *>(p)->cvt_impl(dst, src);
        }
    };
protected:
    class tmp { public: class reso_type {}; };
    typedef ctype<base, tmp> reso_type;
    virtual const reso_type& get_type() const = 0;  // for derived type information
};

class derived : public base {
public:
    template <typename T1, typename T2> void cvt_impl(T1 dst, T2 src) {
        dst = static_cast<T1>(src);
    }
protected:
    virtual const reso_type& get_type() const { 
        static const ctype<derived> d;
        return d;
    }
};

kddoi
記事: 6
登録日時: 9年前

Re: 純粋仮想関数を派生クラスでtemplateを使ってオーバーライド

#8

投稿記事 by kddoi » 9年前

zeek様

追加で返信いただき、ありがとうございます。
usao様のものが、呼び出される関数の時点からデータ型別に分かれており、オーバーライドされた派生クラスの
関数で、それぞれのデータ型に対応したtemplate関数を呼び出す書き方で(説明が不正確かもしれませんが)、
一方でzeek様のものが、基底クラスの関数までは一つの関数で、そこから仮想関数を呼び出す際に
データ型別に分かれ、派生クラスのオーバーライドしたメンバ関数からtemplate関数を呼び出す、
という実装と理解しました。基底クラスの関数の部分で共通した処理を一箇所にまとめて記述した後、
各派生クラスのメンバ関数を呼び出したりすることができそうですね。

またdynamic_castの方は難解でまだ完全には理解できておりませんが、この件とは別に、様々な
データ型を引数とする演算関数を一つのクラスにまとた関数オブジェクトを作ろうかと検討して
おりまして、その構造を考える際に参考になりそうです。ありがとうございます。

閉鎖

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