Что вы пытаетесь сделать здесь
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; }
Важные моменты о том, что происходит до сих пор:
- У меня нет конструктора копирования. В реальной жизни мы переместили логику присваивания в конструктор копирования и реализовали оператор присваивания с помощью copy и swap . Сохраняя код коротким (er) на данный момент.
- Операторы присваивания на самом деле ничего не делают, но они скомпилируют
iff
их «нормальная» версия «делай, что хочешь».
- Есть два оператора доставки; первый для назначения
Aggregate<T>
на Aggregate<T>
и второй для присвоения 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:
Итак ... что мы узнаем из этого?
- Оператор шаблонного присваивания, как написано, не будет компилироваться, если не существует неявного преобразования между агрегированными типами. Это хорошая вещь. Этот код не будет компилироваться, а не падать на вас.
- Проверка на равенство спасла нам только два назначения, и оба они являются указателями (которые настолько дешевы, что их не нужно проверять).
Я бы сказал, что это означает, что проверка на равенство является излишней и должна быть удалена.
Однако, если:
T1
и T2
одного типа или существует неявное преобразование, и
T1
или оператор преобразования T2
стоят дорого (это сразу убирает примитивы из картинки), и
- A
bool operator== (const T1& lhs, const T2& rhs)
, у которого затраты времени выполнения намного меньше, чем у вышеперечисленных операторов присвоения / преобразования
затем проверка на равенство может имеет смысл (зависит от того, как часто вы ожидаете, что operator==
вернет true
).
Заключение
Если вы намереваетесь использовать Aggregator<T>
только с указателями (или любым другим примитивом), тогда тесты на равенство излишни.
Если вы намереваетесь использовать его для создания типов классов с большими затратами, вам понадобится осмысленный оператор равенства, чтобы идти с ними. Если вы используете его также с различными типами, также потребуются операторы преобразования.