Clang-3.5を使用して次の簡単なC++コードをコンパイルしようとしています。
test.h:
class A
{
public:
A();
virtual ~A() = 0;
};
test.cc:
#include "test.h"
A::A() {;}
A::~A() {;}
これをコンパイルするために使用するコマンド(Linux、uname -r:3.16.0-4-AMD64):
$clang-3.5 -Weverything -std=c++11 -c test.cc
そして私が得るエラー:
./test.h:1:7: warning: 'A' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables]
これが警告を発している理由はありますか?仮想デストラクタはまったくインライン化されていません。まったく逆に、test.ccには表外の定義があります。ここで何が欠けていますか?
編集
私はこの質問が: clangの-Wweak-vtablesの意味は何ですか FilipRoséenが示唆したものの複製だとは思わない。私の質問では、純粋な抽象クラスに言及しています(提案された複製には言及されていません)。 -Wweak-vtables
が非抽象クラスでどのように機能するかを知っていて、それで問題ありません。私の例では、実装ファイルでデストラクタ(純粋な抽象)を定義しています。これにより、-Wweak-vtables
であっても、Clangがエラーを出力しないようにする必要があります。
各翻訳単位にvtableを配置したくありません。したがって、vtableを「最初の」翻訳単位に配置するというように、翻訳単位の順序付けが必要になります。この順序が未定義の場合、警告が発せられます。
答えは Itanium CXX ABI にあります。仮想テーブルに関するセクション(5.2.3)には、次のものがあります。
クラスの仮想テーブルは、そのキー機能の定義を含む同じオブジェクトで発行されます。つまり、クラス定義の時点でインラインではない最初の非純粋仮想関数です。キー機能がない場合は、使用されるすべての場所で発行されます。発行された仮想テーブルには、クラスの完全な仮想テーブルグループ、サブオブジェクトに必要な新しい構築仮想テーブル、およびクラスのVTTが含まれます。これらはCOMDATグループで発行され、識別記号として仮想テーブルのマングル名が使用されます。キー関数がクラス定義でインラインで宣言されていないが、後でその定義が常にインラインで宣言される場合、定義を含むすべてのオブジェクトでキー関数が発行されることに注意してください。
[〜#〜] note [〜#〜]:要約では、定義する必要があるため、純粋な仮想デストラクタをキー関数として使用できます。たとえそれが純粋であっても。ただし、ABI委員会は、重要な機能の仕様が完成するまでこの事実を認識していませんでした。 したがって、純粋な仮想デストラクタをキー機能にすることはできません。
2番目のセクションは、質問への回答です。純粋な仮想デストラクタは重要な機能ではありません。したがって、vtableをどこに配置するかは不明であり、どこにでも配置されます。結果として、警告が表示されます。
この説明は Clangソースドキュメント にもあります。
特に警告について:すべての仮想関数が次のカテゴリのいずれかに属している場合、警告が表示されます。
inline
は、クラス定義のA::x()
に指定されています。
struct A {
inline virtual void x();
virtual ~A() {
}
};
void A::x() {
}
B :: x()はクラス定義でインラインです。
struct B {
virtual void x() {
}
virtual ~B() {
}
};
C :: x()は純粋な仮想です
struct C {
virtual void x() = 0;
virtual ~C() {
}
};
(3に属します。)純粋な仮想デストラクタがあります。
struct D {
virtual ~D() = 0;
};
D::~D() {
}
この場合、デストラクタを定義する必要がありますが、定義により「最初の」翻訳単位がまだないため、順序を定義できます。
他のすべての場合、キー関数はこれらのカテゴリのいずれにも当てはまらない最初のvirtual関数であり、vtableはキー関数がある翻訳単位に配置されます定義済み。
しばらくの間、純粋な仮想関数を忘れて、多相クラスの宣言を含むすべての翻訳単位でコンパイラがvtableを発行しないようにする方法を理解してみましょう。
コンパイラーは、仮想関数を持つクラスの宣言を検出すると、宣言されているだけでクラス宣言内で定義されていない仮想関数があるかどうかを確認します。そのような関数が1つだけ存在する場合、コンパイラはmustがどこかに定義されていることを確認し(そうでない場合はプログラムはリンクしません)、その関数の定義をホストする変換単位でのみvtableを発行します。そのような関数が複数ある場合、コンパイラはいくつかの決定的な選択基準を使用してそのうちの1つを選択し、vtableを出力する場所の決定に関して、他の関数を無視します。このような単一の代表的な仮想関数を選択する最も簡単な方法は、候補セットから最初の関数を取得することです。これがclangの機能です。
そのため、この最適化の鍵は、コンパイラーが何らかの変換単位でそのメソッドの(単一の)定義に遭遇することを保証できるように、仮想メソッドを選択することです。
さて、クラス宣言に純粋な仮想関数が含まれている場合はどうなりますか?プログラマーcanは純粋な仮想関数の実装を提供しますが、義務ではありません!したがって、純粋仮想関数は、コンパイラーが代表メソッドを選択できる候補仮想メソッドのリストに属していません。
ただし、1つの例外があります-純粋な仮想デストラクタです!
純粋な仮想デストラクタは特別な場合です:
したがって、質問の例でのclangの警告は概念的に正当化されていません。
ただし、実際の観点からは、純粋な仮想デストラクタが必要な場合はほとんどないため、その例の重要性は最小限です。純粋な仮想デストラクタが別の純粋な仮想関数を伴わない、多かれ少なかれ現実的なケースを想像することはできません。しかし、そのようなセットアップでは、クラスが他の純粋な仮想メソッドの存在により抽象になるため、(仮想)デストラクタの純粋さの必要性は完全になくなります。
純粋な仮想のままにするのではなく、些細な仮想デストラクタを実装することになりました。
代わりに
class A {
public:
virtual ~A() = 0;
};
私が使う
class A {
public:
virtual ~A();
};
次に、.cppファイルに簡単なデストラクタを実装します。
A::~A()
{}
これにより、複数の翻訳単位(オブジェクト)で出力するのではなく、vtableを.cppファイルに効果的に固定し、-Wweak-vtables警告を正常に回避します。
デストラクタを明示的に宣言する副作用として、デフォルトのコピーおよび移動操作を取得できなくなりました。再宣言される例については、 https://stackoverflow.com/a/29288300/954 を参照してください。
これは3つの方法で解決できます。
インラインではない仮想関数を少なくとも1つ使用します。仮想デストラクタの定義も、インライン関数でない限り問題ありません。
以下に示すように、警告を無効にします。
#pragma clang diagnostic Push
#pragma clang diagnostic ignored "-Wweak-vtables"
class ClassName : public Parent
{
...
};
#pragma clang diagnostic pop