C++仮想関数のコンパイルエラー

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

C++仮想関数のコンパイルエラー

#1

投稿記事 by gasbombe » 7年前

C++を勉強中です。

あるクラス内でvirtual関数を定義した場合、それを継承するサブクラスでは必ずしも再定義は必要ない、という認識は合ってますでしょうか。

undefined reference to `vtable for サブクラス名'
というメッセージがコンパイル時に出てエラーになるのですが、原因がよくわかりません。
2行出ているのですが、これは何か2つ、virtualの宣言だけで定義されていないものがあるということですか?
自分では特に見当たらないのですが……。

なにかヒントをいただけないでしょうか。
コンパイラはMinGW gccです。よろしくお願いします。

yuni

Re: C++仮想関数のコンパイルエラー

#2

投稿記事 by yuni » 7年前

こんにちは。

無いと思うコードを載せてもらうと判断しやすいと思うのですが…。
まずご存知かもしれませんが、前提として、
クラスに実装していない関数があると実体を生成できません。ポインタは可能です。
実装していない関数があると実体を生成できない事を利用した仕組みがインターフェイスです。

どうすればサブクラスに関数を定義しなくて済むのかというと、
スーパークラス(ベースクラス)に定義すればいいです。

コード:

class CBase
{
public:
    virtual void Func() {} // 実装
};
class CSub : public CBase
{
public:
    // ここで定義しなくても void CBase::Func(){} は継承してる
    // もちろん定義して上書きしてもOK
};
もしこれが通常の関数宣言のようになっていると実体がないためエラーになります。

コード:

 virtual void Func(); // 実装はどこ?

gasbombe
記事: 4
登録日時: 7年前

Re: C++仮想関数のコンパイルエラー

#3

投稿記事 by gasbombe » 7年前

返信ありがとうございます。

とりあえず、エラーは解消できました。

スーパークラスで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;
}
この差はなんでしょうか。
出力されたコンパイルエラーの意味が結局よくわかりません。

inemaru
記事: 108
登録日時: 7年前

Re: C++仮想関数のコンパイルエラー

#4

投稿記事 by inemaru » 7年前

前提知識が間違っている可能性があるので2点確認です。
・継承の種類について(現在のコードで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;
}
【private継承が意図的でない場合の回答】

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;
}
また、上記のコードに関連して
gasbombe さんが書きました: サブクラスのほうでコンストラクタを作ると、
以下のコードでエラーが出ました。

コード:

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;
}
のコードは、コンストラクタを定義したことが理由でエラーしたわけでなく
SUB::g()の実体がないためエラーをしています。

コード:

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::g() {
	return 3;
}
int SUB::h() {
	return 2;
}

int main() {
	return 0;
}
最後に編集したユーザー inemaru on 2016年8月23日(火) 21:49 [ 編集 1 回目 ]

gasbombe
記事: 4
登録日時: 7年前

Re: C++仮想関数のコンパイルエラー

#5

投稿記事 by gasbombe » 7年前

解説ありがとうございます。
すっきりしました。

ご指摘の通りで、public継承のつもりで書いていました。
private継承というのを全然考えていませんでした。

今回の場合はpublic継承でオーバーライドするのが実際にやりたいことだったのですが、
コンパイルエラーが出なかったので、その関数を呼び出すまでは問題ないのかとか、
あるいはサブクラスでの関数定義がないときはスーパークラスのほうを使うのか、などと思ってしまいました。
定義がないから呼び出せないしクラスのインスタンス化もできない、というので理解しました。
(エラーメッセージがわかりにくいような……)

まだクラスの扱いに慣れないので、また勉強いたします。

閉鎖

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