C++を勉強中です。
あるクラス内でvirtual関数を定義した場合、それを継承するサブクラスでは必ずしも再定義は必要ない、という認識は合ってますでしょうか。
undefined reference to `vtable for サブクラス名'
というメッセージがコンパイル時に出てエラーになるのですが、原因がよくわかりません。
2行出ているのですが、これは何か2つ、virtualの宣言だけで定義されていないものがあるということですか?
自分では特に見当たらないのですが……。
なにかヒントをいただけないでしょうか。
コンパイラはMinGW gccです。よろしくお願いします。
C++仮想関数のコンパイルエラー
Re: C++仮想関数のコンパイルエラー
こんにちは。
無いと思うコードを載せてもらうと判断しやすいと思うのですが…。
まずご存知かもしれませんが、前提として、
クラスに実装していない関数があると実体を生成できません。ポインタは可能です。
実装していない関数があると実体を生成できない事を利用した仕組みがインターフェイスです。
どうすればサブクラスに関数を定義しなくて済むのかというと、
スーパークラス(ベースクラス)に定義すればいいです。
もしこれが通常の関数宣言のようになっていると実体がないためエラーになります。
無いと思うコードを載せてもらうと判断しやすいと思うのですが…。
まずご存知かもしれませんが、前提として、
クラスに実装していない関数があると実体を生成できません。ポインタは可能です。
実装していない関数があると実体を生成できない事を利用した仕組みがインターフェイスです。
どうすればサブクラスに関数を定義しなくて済むのかというと、
スーパークラス(ベースクラス)に定義すればいいです。
class CBase
{
public:
virtual void Func() {} // 実装
};
class CSub : public CBase
{
public:
// ここで定義しなくても void CBase::Func(){} は継承してる
// もちろん定義して上書きしてもOK
};
Re: C++仮想関数のコンパイルエラー
返信ありがとうございます。
とりあえず、エラーは解消できました。
スーパークラスでvirtual宣言、定義した関数で、
サブクラスで宣言だけして実装していないものが1つありました。
サブクラスの関数定義を書いてやることで、エラーはなくなりました。
スーパークラスでの定義が不足しているのだとばかり思って悩んでいました……。
しかし、再現しようと思ってコードを書いてみたのですが、以下のコードではコンパイルしてもエラーにならないんですね。
サブクラスのほうでコンストラクタを作ると、
以下のコードでエラーが出ました。
この差はなんでしょうか。
出力されたコンパイルエラーの意味が結局よくわかりません。
とりあえず、エラーは解消できました。
スーパークラスでvirtual宣言、定義した関数で、
サブクラスで宣言だけして実装していないものが1つありました。
サブクラスの関数定義を書いてやることで、エラーはなくなりました。
スーパークラスでの定義が不足しているのだとばかり思って悩んでいました……。
しかし、再現しようと思ってコードを書いてみたのですが、以下のコードではコンパイルしてもエラーにならないんですね。
class BASE {
public:
virtual int f();
virtual int g();
};
int BASE::f() {
return 0;
}
int BASE::g() {
return 1;
}
class SUB : BASE {
public:
int g();
int h();
};
int SUB::h() {
return 2;
}
int main() {
return 0;
}
以下のコードでエラーが出ました。
class BASE {
public:
virtual int f();
virtual int g();
};
int BASE::f() {
return 0;
}
int BASE::g() {
return 1;
}
class SUB : BASE {
public:
SUB();
int g();
int h();
};
SUB::SUB() {
}
int SUB::h() {
return 2;
}
int main() {
return 0;
}
出力されたコンパイルエラーの意味が結局よくわかりません。
Re: C++仮想関数のコンパイルエラー
前提知識が間違っている可能性があるので2点確認です。
・継承の種類について(現在のコードでprivate継承しているのは意図的なのか)
・virtual関数はオーバーライドするために必要
この前提が間違っていると回答ができません。
以下、すべてこちらの予想で回答するので
質問内容とずれた回答をするかもしれません。
ご了承ください。
【private継承が意図的だった場合の回答】
private継承したスーパークラスのメソッドを
継承先でpublicとして公開するにはオーバーライドが必須になります。
アクセス装飾子を変更するためにプロトタイプ宣言が必須になるので、
実装を書く必要があります。
【private継承が意図的でない場合の回答】
virtualキーワードは必要なく以下のコードで十分です。
次に
virtual関数を使用した際のコードです。
まず、オーバーライドしない場合の例です。
次に、オーバーライドする場合の例です。
【その他】
コンパイルエラーに関して、
インスタンスを使って使用する際にどちらもエラーすると思います。
また、上記のコードに関連して
SUB::g()の実体がないためエラーをしています。
・継承の種類について(現在のコードでprivate継承しているのは意図的なのか)
・virtual関数はオーバーライドするために必要
この前提が間違っていると回答ができません。
以下、すべてこちらの予想で回答するので
質問内容とずれた回答をするかもしれません。
ご了承ください。
【private継承が意図的だった場合の回答】
private継承したスーパークラスのメソッドを
継承先でpublicとして公開するにはオーバーライドが必須になります。
アクセス装飾子を変更するためにプロトタイプ宣言が必須になるので、
実装を書く必要があります。
#include <iostream>
class BASE {
public:
int f();
virtual int g();
};
int BASE::f() {
return 0;
}
int BASE::g() {
return 1;
}
// class SUB : private BASE と class SUB : BASE は同じ
class SUB : private BASE {
public:
SUB();
int g() override; // C++11対応でない場合はoverrideキーワードを削除してください
int h();
};
SUB::SUB() {
}
int SUB::g() {
// 実装は必須
return BASE::g();
}
int SUB::h() {
return 2;
}
int main() {
using namespace std;
cout << BASE().g() << endl;
cout << SUB().g() << endl;
rewind(stdin), getchar();
return 0;
}
virtualキーワードは必要なく以下のコードで十分です。
class BASE {
public:
int f();
int g();
};
int BASE::f() {
return 0;
}
int BASE::g() {
return 1;
}
class SUB : public BASE {
public:
SUB();
int h();
};
SUB::SUB() {
}
int SUB::h() {
return 2;
}
int main() {
using namespace std;
SUB inst;
cout << inst.g() << endl; // BASE::g()が呼ばれている。
rewind(stdin), getchar();
return 0;
}
virtual関数を使用した際のコードです。
まず、オーバーライドしない場合の例です。
#include <iostream>
class BASE {
public:
int f();
virtual int g();
};
int BASE::f() {
return 0;
}
int BASE::g() {
return 1;
}
class SUB : public BASE {
public:
SUB();
// int g() override; // オーバーライドしないのであればプロトタイプ宣言しない
int h();
};
SUB::SUB() {
}
int SUB::h() {
return 2;
}
int main() {
using namespace std;
cout << BASE().g() << endl; // BASE::g()が呼ばれる
cout << SUB().g() << endl; // BASE::g()が呼ばれる
rewind(stdin), getchar();
return 0;
}
#include <iostream>
class BASE {
public:
int f();
virtual int g();
};
int BASE::f() {
return 0;
}
int BASE::g() {
return 1;
}
class SUB : public BASE {
public:
SUB();
int g() override; // C++11対応でない場合はoverrideキーワードを削除してください
int h();
};
SUB::SUB() {
}
int SUB::g() {
// オーバーライドは処理内容を変更するために定義する
// 3を返却するようにする。
return 3;
}
int SUB::h() {
return 2;
}
int main() {
using namespace std;
cout << BASE().g() << endl;
cout << SUB().g() << endl;
rewind(stdin), getchar();
return 0;
}
コンパイルエラーに関して、
インスタンスを使って使用する際にどちらもエラーすると思います。
#include <iostream>
class BASE {
public:
virtual int f();
virtual int g();
};
int BASE::f() {
return 0;
}
int BASE::g() {
return 1;
}
class SUB : BASE {
public:
int g(); // BASE::g()を公開するには実体が必要
int h();
};
int SUB::h() {
return 2;
}
int main() {
using namespace std;
cout << SUB().g() << endl; // 実体が無いのでエラー
return 0;
}
のコードは、コンストラクタを定義したことが理由でエラーしたわけでなく
SUB::g()の実体がないためエラーをしています。
最後に編集したユーザー inemaru on 2016年8月23日(火) 21:49 [ 編集 1 回目 ]
Re: C++仮想関数のコンパイルエラー
解説ありがとうございます。
すっきりしました。
ご指摘の通りで、public継承のつもりで書いていました。
private継承というのを全然考えていませんでした。
今回の場合はpublic継承でオーバーライドするのが実際にやりたいことだったのですが、
コンパイルエラーが出なかったので、その関数を呼び出すまでは問題ないのかとか、
あるいはサブクラスでの関数定義がないときはスーパークラスのほうを使うのか、などと思ってしまいました。
定義がないから呼び出せないしクラスのインスタンス化もできない、というので理解しました。
(エラーメッセージがわかりにくいような……)
まだクラスの扱いに慣れないので、また勉強いたします。
すっきりしました。
ご指摘の通りで、public継承のつもりで書いていました。
private継承というのを全然考えていませんでした。
今回の場合はpublic継承でオーバーライドするのが実際にやりたいことだったのですが、
コンパイルエラーが出なかったので、その関数を呼び出すまでは問題ないのかとか、
あるいはサブクラスでの関数定義がないときはスーパークラスのほうを使うのか、などと思ってしまいました。
定義がないから呼び出せないしクラスのインスタンス化もできない、というので理解しました。
(エラーメッセージがわかりにくいような……)
まだクラスの扱いに慣れないので、また勉強いたします。