ここでやろうとしていること
if (this != static_cast< A<T>* > (&rhs) )
static_cast
からを実行しA<derived*>
ますA<base*>
。
内容static_cast
は次のとおりです。
AがBの基本クラスである場合、タイプAのポインターをタイプBのポインターに明示的に変換できます。AがBの基本クラスでない場合、コンパイラー・エラーが発生します。
A<base*>
はの基本クラスではないA<derived*>
ため、エラーが発生します。
一般に、がに変換可能であっても、明らかA<T>
にの基本クラスになることはありません。そのため、キャストは方程式から外れています。A<X>
X
T
reinterpret_cast<void*>(&rhs)
解決策は、代わりに使用することです。
アップデート
私はこれにもう少し取り組みました。結果は次のとおりです。最初にコードを示し、次にコメントします。
セットアップコード
template <typename T>
class Aggregator {
public:
Aggregator() {
std::cout << "Default Constructor" << std::endl;
}
Aggregator(const T& t) : m_t(t) {
std::cout << "Constructor With Argument" << std::endl;
}
Aggregator& operator= (const Aggregator& rhs)
{
std::cout << "Assignment Operator (same type)";
if (this->get() == rhs.get()) {
std::cout << " -- SKIPPED assignment";
}
else {
T justForTestingCompilation = rhs.get();
}
std::cout << std::endl;
return *this;
}
template <class U>
Aggregator& operator=(const Aggregator<U>& rhs)
{
std::cout << "Assignment Operator (template)";
if (this->get() == rhs.get()) {
std::cout << " -- SKIPPED assignment";
}
else {
T justForTestingCompilation = rhs.get();
}
std::cout << std::endl;
return *this;
}
T get() const { return m_t; }
private:
T m_t;
};
class base {};
class derived : public base {};
class unrelated {};
// This is just for the code to compile; in practice will always return false
bool operator==(const base& lhs, const base& rhs) { return &lhs == &rhs; }
これまでに起こっていることに関する重要なポイント:
- コピーコンストラクターはありません。実際には、代入ロジックをコピーコンストラクターに移動し、コピーとスワップを使用して代入演算子を実装します。今のところコードを短くしておきます。
- 代入演算子は実際には何もしませんが
iff
、「通常の」、必要に応じて実行するバージョンをコンパイルします。
- 2つのアッシング演算子があります。1つ目はに割り当てる
Aggregate<T>
ためのものAggregate<T>
で、2つ目はに割り当てるAggregate<T1>
ためのものAggregate<T2>
です。これはトニーの答えから直接であり、それは「正しい方法」です。
operator==
暗黙的に定義されていない型の比較演算子を偽造するための自由があります。具体的には、がプリミティブ型でないAggregate<U>
場合にコンパイルするを含むコードに必要です。U
エクササイズコード
ここにすべての楽しみがあります:
int main(int argc, char* argv[])
{
base b;
derived d;
unrelated u;
Aggregator<base*> aggPB(&b);
Aggregator<base*> aggPBDerivedInstance(&d);
Aggregator<derived*> aggPD(&d);
Aggregator<unrelated*> aggPU(&u);
Aggregator<base> aggB(b);
Aggregator<base> aggBDerivedInstance(d); // slicing occurs here
Aggregator<derived> aggD(d);
Aggregator<unrelated> aggU(u);
std::cout << "1:" << std::endl;
// base* = base*; should compile, but SKIP assignment
// Reason: aggregate values are the same pointer
aggPB = aggPB;
// base = base; should compile, perform assignment
// Reason: aggregate values are different copies of same object
aggB = aggB;
std::cout << "2:" << std::endl;
// base* = base*; should compile, perform assignment
// Reason: aggregate values are pointers to different objects
aggPB = aggPBDerivedInstance;
// base = base; should compile, perform assignment
// Reason: aggregate values are (copies of) different objects
aggB = aggBDerivedInstance;
std::cout << "3:" << std::endl;
// base* = derived*; should compile, perform assignment
// Reason: aggregate values are pointers to different objects
aggPB = aggPD;
// base = derived; should compile, perform assignment (SLICING!)
// Reason: derived is implicitly convertible to base, aggregates are (copies of) different objects
aggB = aggD;
std::cout << "4:" << std::endl;
// base* = derived*; should compile, but SKIP assignment
// Reason: aggregate values are (differently typed) pointers to same object
aggPBDerivedInstance = aggPD;
// base = derived; should compile, perform assignment (SLICING!)
// Reason: derived is implicitly convertible to base, aggregates are (copies of) different objects
aggBDerivedInstance = aggD;
std::cout << "5:" << std::endl;
// derived* = base*; should NOT compile
// Reason: base* not implicitly convertible to derived*
// aggPD = aggPB;
// derived = base; should NOT compile
// Reason: base not implicitly convertible to derived
// aggD = aggB;
return 0;
}
これは出力します:
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
1:
Assignment Operator (same type) -- SKIPPED assignment
Assignment Operator (same type)
2:
Assignment Operator (same type)
Assignment Operator (same type)
3:
Assignment Operator (template)
Assignment Operator (template)
4:
Assignment Operator (template) -- SKIPPED assignment
Assignment Operator (template)
5:
それで...私たちはこれから何を学びますか?
- テンプレート化された代入演算子は、記述されているように、集約された型の間に暗黙の変換がない限りコンパイルされません。それは良いことです。このコードは、クラッシュするのではなく、コンパイルに失敗します。
- 同等性のテストでは、2つの割り当てしか保存されませんでした。どちらもポインターの割り当てです(非常に安価であるため、チェックする必要はありません)。
これは、同等性チェックが不要であり、削除する必要があることを意味します。
ただし、次の場合:
T1
とT2
は同じタイプであるか、暗黙の変換が存在し、
T1
の代入演算子またはT2
の変換演算子は高価であり(これにより、プリミティブがすぐに画像から削除されます)、
bool operator== (const T1& lhs, const T2& rhs)
上記の割り当て/変換演算子よりも実行コストがはるかに小さいA
次に、同等性をチェックすることは理にかなっているかもしれませんoperator==
(あなたが戻ると期待する頻度に依存しますtrue
)。
結論
Aggregator<T>
ポインタ型(またはその他のプリミティブ)だけで使用する場合は、等価性テストは不要です。
クラス型を構築するために高価なものと一緒に使用する場合は、それらを使用するために意味のある等式演算子が必要になります。異なるタイプでも使用する場合は、変換演算子も必要になります。