• ベストアンサー

オーバーロードしたメンバ関数が継承後,利用できない

オーバーロードしたメンバ関数のクラスを継承後,そのメンバ関数と同名のメンバ関数を定義すると,オーバーロードしたメンバ関数が呼び出せなくなります.何故でしょうか? 質問を一言で正確に表現できてないと思いますので,状況を順を追って説明します. まず,クラスT1にメンバ関数m(int)があったとします. そのメンバ関数をオーバーロードしてm(float)を作ったとします. そして,T1を継承してT2を定義し,その中でメンバ関数m(int)をオーバーライドすると,T2のオブジェクトではm(float)が呼び出せなくなってしまいました. 何故この様なことをするかといいますと,m(string)はstringの引数をある関数で変換して整数にしてから,intでm(int)を呼び出しているとします. T2でも,stringによってm(int)を呼び出すので,T1で定義したm(string)をT2でもそのまま使いたいのです. 例です class T1{ public: virtual void m(int i){cout<<i<<"T1\n";} void m(string s){m((int)s[0]);} }; class T2{ public: virtual void m(int i){cout<<i<<"T2\n";} }; int main(){ T2 t; string s("a"); // t.m(s); /*error*/ t.T1::m(s); } 出力 97T2 T1のm(string)はT2のm(int)を呼び出してます. 何か解決策ありますでしょうか? また,言語の仕様がこの様になってるのは,この様なことをすると問題があるからだと思うのですが,どのような問題が起こるので禁止されてるのでしょう? それとも実は,この様なことはできるのでしょうか? (つまり,単に私のプログラムが間違ってる)

  • bilbo
  • お礼率57% (11/19)

質問者が選んだベストアンサー

  • ベストアンサー
noname#11476
noname#11476
回答No.5

まず用語について、申し訳ありません。 確かに。私が間違って覚えていました。(穴があったら、、、) さて、ご質問の意図は良くわかりましたが、やはり本質的な問題は多重定義した場合は、そのうち一つでもオーバーライドすると全体が隠されてしまうため、T3からT1のメンバ関数に直接アクセスできない(つまりT1のm(const char *)が使えない)ことにありますよね。 結局(多重定義関数は)引数が違うだけでひとまとまりの一つのメンバ関数として扱われているという点です。(そうなっている理由は既に述べたとおりです) 私の思いつく解決策としては、たとえば、 class T1 { public: void m(int i) {mm(i);}; void m(char *s){テーブル検索;mm(i);}; protected: virtual void mm(int i);//mm(int i)にm(i)の処理を移動 }; class T3 : public T1 { protected: void mm(int i); }; と変更する実体の関数を別に用意すれば、そちらはオーバライドされてもかまわないのでこの問題は解決します。 (これがエレガントかといわれると、???ですが、、、T3を作る人にとっては最小限の変更ですみますよね。) 結局のところT1クラスに手を入れない、かつT3で(m(string)を)定義しなおさないというと解決策は無いように思えます。 どんなものでしょうか。 個人的にはT3でT1のm(const char*)を呼び出すm(const char*)を再定義するよりはましかなと思うのですが。 (今回は多重定義一つだから良いけど、10個くらいあるとそのうち一個の変更のために全部は書きたくないですから) C++からこの制約を取り除こうとすると、暗黙の型変換を禁止しないと出来ないように思えますね。 (なぜいっそのこと暗黙の型変換を禁止にしなかったのかは分からないですね。きっといろんな理由があるんでしょうね。) 実のところ、私はテスト的に色々変更する可能性のあるクラスを作るときはよくこの手を使います。 つまり、インターフェース関数はあくまで顔にして実体の関数はprotectedとかprivateに隠してしまう。 こうすると、後でいろんな変更がしやすいんですよね。たとえオーバヘッドがあっても。

bilbo
質問者

お礼

ご解答ありがとうございます. お陰様でプログラムの問題点を,ほぼ完全に取り除けました. これで安心して続きを作成できるというものです. >確かに。私が間違って覚えていました。(穴があったら、、、) 事の発端は私が質問文を間違えていた事ですから,全く気にすることはないと思いますよ.

その他の回答 (4)

noname#30727
noname#30727
回答No.4

言葉が足りなかったので、少し追加します。 >ただ,これだと派生クラスの方で新しく定義したメンバ関数は,呼び出せなくなってしまうという問題がありますね. その目的の為に、dynamic_cast というのが後から追加されたのですが、 やや無理矢理的な部分を感じていて、私は積極的には使っていません。 >それでも,基底クラスのインターフェイスの設計を頑張れば,解決できるような問題かと思えますので,実用的な解決法と言えますね. 私は、これが C++ の限界と認識しています。 JAVA や C# のような言語では、この問題を一応解決しているのが大きいと思います。

bilbo
質問者

お礼

解答ありがとうございます. > JAVA や C# のような言語では、この問題を一応解決しているのが大きいと思います。 確かにJAVAによるインターフェイスの設計は,C++よりも簡単かつ簡潔と言える部分が結構ありますね. JAVAには,C++のSTLと違って(特にVisual C++のやつ),ライブラリもきちんとしたのが揃ってますから魅力的な言語ではありますね.

noname#30727
noname#30727
回答No.3

>最後に1つ疑問に思ったのですが,参照による動的結合は可能だったでしょうか? わかりやすくする為に、関数を1つあいだにいれますが、 void sub(T1& t){ string s("a"); t.m(s); } int main(){ T1 t1; T2 t2; sub(t1); sub(t2); return 0; } こんな感じです。

noname#30727
noname#30727
回答No.2

解決策ですが、 virtual を使う場合は、基底クラスへのポインタ(もしくは参照)を使ってアクセスするしかないと思います。 int main(){ T1 *t1 = new T1; T1 *t2 = new T2; // ここが重要 string s("a"); t1->m(s); t2->m(s); delete t1; delete t2; return 0; }

bilbo
質問者

お礼

回答ありがとうございます. 非常に興味深い回答です. ただ,これだと派生クラスの方で新しく定義したメンバ関数は,呼び出せなくなってしまうという問題がありますね. それと,実際にオブジェクトを使う時に,無用なミスを防ぐためにも,質問の様なことを意識しないで呼び出せるようにしたいです. それでも,基底クラスのインターフェイスの設計を頑張れば,解決できるような問題かと思えますので,実用的な解決法と言えますね. 最後に1つ疑問に思ったのですが,参照による動的結合は可能だったでしょうか? Javaなどのことを考えると,動的結合のことは混乱してしまいますね. もし知っておられましたら,再び解答頂けると非常にありがたいです.

noname#11476
noname#11476
回答No.1

ご質問の内容を理解するために確認します。 ご説明とソースが著しく食い違っているので、ご説明のほうを正としますね。 (ソースコード自体動かないはずです) class T1 { public:  virtual void func(int i);  virtual void func(float a); }; というように多重定義したわけですね?(これはオーバーロードではありませんよ) で、派生クラスを、 class T2 : public T1 { public:  void func(char *s); }; と書いたのですね。 これは、class T2 の T2::func(char *s) で基底クラスT1の関数 T1::func()をオーバーロード(今度はオーバーロードです)しています。 つまり、基底クラスのT1::funcは再定義されましたので全部見えなくなります。 つまり、 T2 t; t.func(1); t.func(1.0); は T2::func(char *)を呼び出してしまいます。(コンパイルの型チェックで引っかかると思いますが) 基本的にクラスの親子をまたがって「多重定義」は出来ません。(C++の仕様です。理由はあとで) 解決策はいくつかあります。 1.正攻法 考えてみてください。そもそも派生クラスでそのような同一の働きをする関数を加えることはおかしな話です。 その場合は、普通基底クラスにそのfunc(char *s)を追加すべきなのです。 たとえ基底クラスを変更しても何も問題は起きません。なぜならうまく隠蔽されているからです。 2.邪道1 T2 t; t.T1::func(1); t.T1::func(1.0); はきちんと働きます。(bilboさんのテストがうまくいかなかったのはソースコードに問題があると思いますよ) 3.邪道2 class T2 : public T1 { public:  void func(int i) {T1::func(i);};  void func(float a) {T1::func(a);};  void func(char *s); }; と再定義して、T2::func(int i)の中で、T1::func(i)を呼び出せばよい。 ほら、書いてみると分かりますが、これなら初めから基底クラスのT1を変更すべきでしょう。 さて、なぜ派生クラスと基底クラスを含めた多重定義が出来ないのかについて述べますね。 もし、多重定義が出来るとどういうことになるかお話します。 まずA君が基底クラス base を作りました。 class base { public:  virtual void func(int i); }; B君はこれを使って派生クラスderivedを作りました。そして、func2というメンバ関数を作りました。 class derived : public base { public:  void func2(double a); }; さて、A君は色々考えるうちに基底クラスbaseを改良しようと思い、新しい関数を加えることにしました。 そこで、 class base { public:  virtual void func(int i);  virtual void func2(float a); }; と新しい関数を加えました。もちろんこの関数func2はB君が派生クラスで作った関数func2とは異なる働きをします。 ここで問題が発生します。A君は自分の作ったクラスbaseがB君によってどのように使われているかは分かっていなかったので、B君が作った関数と同じ名前で作ってしまったのです。 もし、A君が  virtual void func2(double a); と同じ引数であれば、関数はオーバライドされて問題はなかったでしょう。 しかし、この場合は多重定義になりますから、オーバーライドされなかったわけです。 で、B君のプログラムの中で、 derived foo; float a; foo.func2(a); と呼び出されていたとすると、A君が基底クラスを変更する前であれば、上記プログラムは、 derived::func2(double) を呼び出していました。(コンパイル時にはワーニングが出ますけどね。エラーではありません。) しかし、A君が変更した新しいbaseを使うと、なんと!!A君のfunc2を呼び出してしまうではありませんか。 つまり、基底クラスの変更が派生クラスに影響を及ぼしてしまったのです。 こういうことを避けるために、出来ないことになっています。 では。

bilbo
質問者

お礼

丁寧な回答ありがとうございます. 早めに回答戴けてありがたかったです. 質問文が直り切ってなかったのは申し訳なかったですが,プログラムの方も質問文の方も大体は意図は同じです. ただ,floatとintですと,暗黙の型変換が出来るため,関数の定義がm(float)とm(int)で曖昧になってしまい,コンパイラから文句を言われるので直したのですが,直しきれてなかったと言う経緯があります. まず,ひとつお尋ねしたいのですが,T2でvoid func(char *s)を定義する事がオーバーロードと言うのでしたら,オーバーライドとは何を意味しているのでしょうか. 演算子のオーバーロードという,重要ですが紛らわしいのがありますから,どっちがどっちだったか分からなくなる事がありますが... web上に用語解説がありましたので,恐らく多重定義のことをオーバーライドと言うと思うのですが,どうでしょうか? http://mikata.curiocube.com/oop/terms.html どうも質問文の意味が曖昧すぎたようですので,もう少し細かく具体的に書きたいと思います. 私が定義していたクラス(クラスT1とします)は,他のクラス(クラスT2とします)を配列で管理する目的のものでした. クラスT2は名前のメンバを持っているとしまして,それをstringで表現していました. クラスT1でT2を管理する際に,配列中のどのT2に対して処理を行うかを,配列のインデックスかもしくはT2のオブジェクトの名前で指定したいと考えました. そこでT1のメンバ関数として,m(int index)と,m(const string &str)を用意することにしました. m(const string &str)は,対応表からstrに対するindexを引きまして,それをm(int index)に渡すというだけのメンバ関数で,処理の本体はm(int index)の方に定義していました. ここで,T1からクラスを派生する(クラスT3とします)ことを考えるのですが,T1とT3で異なっているのは,m(int index)の定義だけです. ですから,名前からindexを引いてm(int index)に渡すだけのm(string &str)は,T3ではヘッダファイルが見辛くなるので一々定義したくないと考えました. そこで,m(int index)をT1とT3でvirtualにして動的結合してやれば,T1で定義したm(string &str)は,T3のオブジェクトではT3のm(int index)を呼び出してくれると期待しました. 以上が質問の背景です. そこで再び質問しなおしますが,T3のオブジェクトからT1のm(string &str)を使って,T3のm(int index)を呼び出す方法はないでしょうか? 確かにt3.T1::m(str)としてやれば,問題なく呼び出せますが,他のメンバ関数もありますので,これは使っていて混乱の元です. これを読んでくれることを切望しながら,失礼させていただきます.

関連するQ&A

  • 継承元の関数はオーバーロードできないのですか?

    #include <stdlib.h> /* 動物クラス */ class CAnimal{ public: int type; void init(int); void prt(void); }; /* 犬クラス */ class CDog : public CAnimal{ public: void init(void); }; /*------------ 動物クラス ------------*/ void CAnimal::init(int t){ type = t; } void CAnimal::prt(void){ printf("%d\n", type); } /*------------ 犬クラス ------------*/ void CDog::init(void){ type = 10; } /*------------ メイン関数 ------------*/ void main(void) { CDog dog; dog.init(1); dog.prt(); } メイン関数の「dog.init(1)」(下から3行目)でコンパイルエラーが出ます。継承元の関数はオーバーロードできないのでしょうか? 参考書にはそんなこと書いてありませんし、検索しても似たような事例が見つかりません。 初歩的なことですみませんが、どなたか教えていただけないでしょうか。

  • c++のvirtual関数および継承について

    以下のプログラムで間違っている部分が分かる方、ご指摘お願い致します。 私の稚拙な腕では、どこが間違っているのかさっぱり分かりません。 以下のプログラムは、クラスticketを継承して、airplaneTicket、cinemaTicketを作成し、チケットの情報を入力し、最後に出力するものです。コンパイルエラーの箇所はコメントアウトしておきました。メイン関数の中に計5箇所あります。 よろしくお願い致します。 #include<string> #include<iostream> #include<vector> using namespace std; class ticket{ public: string ID; string seat; int price; ticket(){ cout << "ticket default constructor" << endl; ID="not Known"; seat="not Known"; } ticket(string i, string t){ cout << "ticket parameter-constructor" << endl; ID=i; seat=t; } virtual void setPrice(int p)=0; void printInfo(){ cout << ID << " " << seat << endl; } }; class airplaneTicket: public ticket{ public: string flight; airplaneTicket(){ cout << "airplane default constructor" << endl; flight="not known yet"; } airplaneTicket(string i, string t, string f):ticket(i, t){ cout << "airplane parameter-constructor" << endl; flight=f; } virtual void setPrice(int p){ string type; cout << "enter class type: "; cin >> type; if(type=="business") price=p*2; else price=p; } void priceInfo(){ cout << ID << " " << seat << " " << price << " " << flight << endl; } }; class cinemaTicket: public ticket{ public: cinemaTicket(){ cout << "cinema default constructor" << endl; } cinemaTicket(string i, string t):ticket(i, t){ cout << "cinema parameter-constructor" << endl; } }; int main(){ ticket tList[100]; //error: invalid abstract type 'ticket' for 'tList' tList[0]=new airplaneTicket("239", "d34", "f345"); //error: no match for 'operator=' in 'tList[0] tList[1]=new airplaneTicket(); // error: no match for 'operator=' in 'tList[1] tList[2]=new cinemaTicket("245", "a23"); //error: cannot allocate an object of abstract type 'cinemaTicket' tList[3]=new cinemaTicket(); // error: cannot allocate an object of abstract type 'cinemaTicket for(int i=0;i<4; i++){ tList[i].printInfo(); } return 1; }

  • オーバーロードされたメンバ関数のポインタをとる

    オーバーロードされたメンバ関数のポインタをとるにはどうしたらよいでしょうか? 具体的には前置インクリメント演算子 ++()と、後置インクリメント演算子 ++(int)の両方のをオーバーロードしたときに両方のメンバ関数ポインタがとりたいです。 環境はVC7.1です。 よろしくお願いします。

  • メンバ関数ポインタを値とするコンテナの定義方法

    こんにちは、boundaryといいます。 あるクラスのメンバ関数のポインタをmapで管理したいので すが、定義でエラーになってしまいます。 class CFunc { private: bool FuncA(); bool FuncB(); }; このようなクラスに対して、 template <class T> class FuncMap { private: typedef bool (T::*Func)(); typedef map<int, T::Func> _FuncMap; _FuncMap FMap; public: void set(int, T::Func); T::Func search(int); }; を用意しました。 template <class T> void FuncMap<T>::set(int i, T::Func pFunc) { FMap.insert(pair<int , T::Func>(i ,pFunc)); } template <class T> T::Func FuncMap<T>::search(int i) { map<T::Func, int>::iterator FuncMapIte; FuncMapIte = FMap.find(i); return T::Func; } と書いたのですが、 error C2244: 'FuncMap<T>::set' : 関数のオーバーロードが解決できません。 error C2954: テンプレートの定義はネストできません。 error C2244: 'FuncMap<T>::search' : 関数のオーバーロードが解決できません。 との事です。 色々試行錯誤したのですが、うまくいきません。 定義のどこがいけないのでしょうか? よろしくお願いします。 VC6.0SP5 WINDOWS2000です。

  • C++言語 メンバ関数

    何気なく使用しているクラスのメンバ関数、これの中身が実際どうなってるか知りたいです。大別して2種類に分かれますので以下の2つを 教えて下さい。また詳しく載っているサイトなどあれば教えて下さい。 1,stringクラスMid(int start,int length関数と operator関数+について class String{ char *p; char *q; public: //各定義 }; String String::Mid(int start,int length) const { // } String String::operator+(const String&s)const{ return String(p+s.q+s.p); } 最初の方はイメージもつかず、2つめのほうはポインタに ポインタを加えようとしましたとでます。 教えて下さい。

  • str メンバ関数について

    str メンバ関数について 下記のstring Date::to_string() const のstr()メンバ関数が日付クラスDate、 (インタフエース部、実装部)に定義等が記入されていないので、何処に定義されているか教えてください。 ************************************************************* // 日付クラスDate(第2版:実装部) #include <ctime> #include <sstream> #include <iostream> #include "Date.h" using namespace std; //--- 文字列表現を返却 ---// string Date::to_string() const { ostringstream s; s << year << "年" << month << "月" << day << "日"; return s.str(); } ****************************************************************** // 日付クラスDate(第2版:インタフェース部) #include <string> #include <iostream> using namespace std; class Date { int year; // 西暦年 int month; // 月 int day; // 日 public: Date(); // デフォルトコンストラクタ Date(int y, int m = 1, int d = 1); // コンストラクタ int Year() const { return year; } // 年を返却 int Month() const { return month; } // 月を返却 int Day() const { return day; } // 日を返却 string to_string() const; // 文字列表現を返却 }; ostream& operator<<(ostream& s, const Date& x); // 挿入子

  • メンバ変数が変更されそうなconstメンバ関数

    c++のconstメンバ関数についての質問です。 以下のコードの様に、constメンバ関数で メンバ変数を変更しているように見える場合、 本来はどのように記述すべきか? class foo { public: foo(){} foo(int i){ d = i; } void hoge(foo *dest0, foo *dest1 ) const { dest0->d = d*2; dest1->d = d*4; } int d; }; int main() { foo f(1); foo p,q; f.hoge(&p,&q); std::cout << f.d << "\n"; std::cout << p.d << "\n"; std::cout << q.d << "\n"; f.hoge(&f,&q); std::cout << f.d << "\n"; std::cout << q.d << "\n"; return 0; } ちなみに出力期待値は 1 2 4 2 4 とします。 上のコードは 1 2 3 2 8 と出力されますが、このようなことが無いように実装するには どうすべきかという質問です。 dest0->d = d*2; dest1->d = d*4; の所を int i=d; dest0->d = i*2; dest1->d = i*4; とすべき? それともhogeの最初に if( (this == dest0) || (this == dest1) || (dest0== dest1) ){ throw "同じオブジェクトじゃだめ"; } とすべき? どんな書き方が安定でしょうか?

  • 継承したクラスを、継承元のクラス型の引数に渡すとどうなるのでしょうか?

    継承したクラスを、継承元のクラス型の引数に渡すとどうなるのでしょうか? 以下のようなケースで、 #include "stdio.h" using namespace std; // baseクラス class base { private:  int m_nII;  int m_nJJ;  int m_nKK; public:  base(int i,int j,int k){ m_nII=i; m_nJJ=j; m_nKK=k; }  int GetSum(){ return (m_nII+m_nJJ+m_nKK); } }; // base 継承クラス class hoge : public base { public:  hoge() : base(1,2,3){} }; void func(base* obj){ // baseクラスを引数に取る関数  printf("sum is %d\n", obj->GetSum()); } // main int main(){  hoge objHoge;  func((base*)&objHoge); // <-キャストして渡す  return 0; } として、一応、gccでコンパイルは通り、実行結果も期待通りだったのですが、 このやり方で問題は無いのでしょうか? (たとえば継承先のクラスが独自のメンバを持っていたりなどした場合、期待した結果にならないとか・・) よろしくお願いします。

  • 仮想関数と継承について

    #include <iostream> using namespace std; class AAA { public: virtual aaa() { cout <<"aaa(void)"<<endl;} }; class BBB : public AAA { public: virtual aaa(int a) { cout <<"aaa(int)"<<a<<endl;} }; int main() { BBB bbb; bbb.aaa();<--これがエラーになります。 return 0; } どうして、既定クラスの引数なしのaaa()はよぶことが できないのでしょうか?

  • ->*演算子のオーバーロードについて

    こんにちは。質問させてください。 現在下記のような処理(main関数でやっているような処理)を実現させたいのですが、うまくコンパイルできません。 #include <iostream> class Test { public:   void TestFunc()   {     std::cout << "TestFunc" << std::endl;   } }; class AllowOverLoad { public:   Test* operator ->()   {     return new Test;   } }; int main() {   void ( Test::*lpTestFunc )() = &Test::TestFunc;   AllowOverLoad overload;   ( overload->*lpTestFunc )(); } 主なエラーは error C2296: '->*' : 無効です。左オペランドには型 'AllowOverLoad' が指定されています。 です。 おそらくこの問題を解決するにはAllowOverLoadに->*演算子をオーバーロードしなくてはいけないと思うのですが、->*演算子のオーバーロード方法がいまいちよくわかりません。 いい文献やHPも見つけられなかったので質問させていただきました。 /* 現在、本番のコーディングでは暗黙的なキャストを禁止しているのでAllowOverLoadクラスに暗黙的にTest*型にキャストするような処理はなしの方向で、あくまで->*演算子のオーバーロードということでお願いします。 Microsoft WindowsXP Professional Edition VisualStudio 2008 AcademicEdition */ よろしくお願いします。

専門家に質問してみよう