偶然見つけた奇妙な例を皆さんと共有したいと思い、それで 2 日間考え続けました。
この例が機能するには、次のものが必要です。
- 三角形の仮想継承 (メンバー関数上
getAsString()
) - テンプレート クラスのメンバー関数の特殊化 (ここでは
Value<bool>::getAsString()
) 仮想関数をオーバーライドする - コンパイラによる (自動) インライン化
共通のインターフェース、つまり一連の仮想関数を仮想的に継承するテンプレート クラスから始めます。後で、これらの仮想機能の 1 つを特殊化します。インライン化すると、スペシャライゼーションが見過ごされる可能性があります。
// test1.cpp and test2.cpp
#include <string>
class ValueInterface_common
{
public:
virtual ~ValueInterface_common() {}
virtual const std::string getAsString() const=0;
};
template <class T>
class Value :
virtual public ValueInterface_common
{
public:
virtual ~Value() {}
const std::string getAsString() const;
};
template <class T>
inline const std::string Value<T>::getAsString() const
{
return std::string("other type");
}
次に、このクラスと、それ自体もテンプレート化する必要がValue
あるクラスのインターフェイスを継承する必要があります。Parameter
// test1.cpp
template <class T>
class Parameter :
virtual public Value<T>,
virtual public ValueInterface_common
{
public:
virtual ~Parameter() {}
const std::string getAsString() const;
};
template<typename T>
inline const std::string Parameter<T>::getAsString() const
{
return Value<T>::getAsString();
}
Value
今、 bool と等しい型の特殊化の前方宣言を与えないでください (!) 。
// NOT in: test1.cpp
template <>
const std::string Value<bool>::getAsString() const;
しかし、代わりに、このように定義するだけです...
// test2.cpp
template <>
const std::string Value<bool>::getAsString() const
{
return std::string("bool");
}
..しかし、別のモジュールで(それは重要です)!
最後に、main()
何が起こっているかをテストする関数があります。
// test1.cpp
#include <iostream>
int main(int argc, char **argv)
{
ValueInterface_common *paraminterface = new Parameter<bool>();
Parameter<int> paramint;
Value<int> valint;
Value<bool> valbool;
Parameter<bool> parambool;
std::cout << "paramint is " << paramint.getAsString() << std::endl;
std::cout << "parambool is " << parambool.getAsString() << std::endl;
std::cout << "valint is " << valint.getAsString() << std::endl;
std::cout << "valbool is " << valbool.getAsString() << std::endl;
std::cout << "parambool as PI is " << paraminterface->getAsString() << std::endl;
delete paraminterface;
return 0;
}
次のようにコードをコンパイルすると (test1.cpp と test2.cpp という名前の 2 つのモジュールにコードを配置し、後者には特殊化と必要な宣言のみが含まれます):
g++ -O3 -g test1.cpp test2.cpp -o test && ./test
出力は
paramint is other type
parambool is other type
valint is other type
valbool is bool
parambool as PI is other type
-O0
またはだけでコンパイルした場合、または-fno-inline
特殊化の前方宣言を行った場合、結果は次のようになります。
paramint is other type
parambool is bool
valint is other type
valbool is bool
parambool as PI is bool
おかしいですね。
これまでの私の説明は次のとおりです。インライン化は最初のモジュール (test.cpp) で機能しています。必要なテンプレート関数はインスタンス化されますが、一部は への呼び出しでインライン化されてしまいますParameter<bool>::getAsString()
。一方、valbool
これは機能しませんでしたが、テンプレートはインスタンス化され、関数として使用されます。次に、リンカーは、インスタンス化されたテンプレート関数と、2 番目のモジュールで指定された特殊化されたテンプレート関数の両方を見つけ、後者を決定します。
どう思いますか?
- この動作はバグだと思いますか?
- どちらも仮想関数をオーバーライドしているのに、インライン化
Parameter<bool>::getAsString()
が機能するのに機能しないのはなぜですか?Value<bool>::getAsString()