Почему указатель на производный класс не может быть передан функции, ожидающей ссылки на указатель на базовый класс?
Извините за длинный заголовок, но я хочу быть конкретным.
Я ожидал, что следующий код будет работать, но это не так, и я не могу понять, почему:/
#include <cstdio>
#include <cassert>
class UniquePointer
{
public:
void Dispose()
{
delete this;
}
friend void SafeDispose(UniquePointer*& p)
{
if (p != NULL)
{
p->Dispose();
p = NULL;
}
}
protected:
UniquePointer() { }
UniquePointer(const UniquePointer&) { }
virtual ~UniquePointer() { }
};
class Building : public UniquePointer
{
public:
Building()
: mType(0)
{}
void SetBuildingType(int type) { mType = type; }
int GetBuildingType() const { return mType; }
protected:
virtual ~Building() { }
int mType;
};
void Foo()
{
Building* b = new Building();
b->SetBuildingType(5);
int a = b->GetBuildingType();
SafeDispose(b); // error C2664: 'SafeDispose' : cannot convert parameter 1 from 'Building *' to 'UniquePointer *&'
b->Dispose();
}
int main(int argc, char* argv[])
{
Foo();
return 0;
}
Ответы
Ответ 1
Представьте, что это было законно. Затем вы можете написать код следующим образом:
class Animal : public UniquePointer
{
};
void Transmogrify(UniquePointer*& p)
{
p = new Animal();
}
void Foo()
{
Building* b = nullptr;
Transmogrify(b);
b->SetBuildingType(0); // crash
}
Соблюдайте, что вы нарушили систему типов (вы ставите Animal, где должно быть здание), не требуя приведения или повышения ошибки компилятора.
Ответ 2
Я не думаю, что можно заставить его работать так, как вы его разработали. Вместо этого попробуйте следующее:
template <typename T>
void SafeDispose(T * & p)
{
if (p != NULL)
{
p->Dispose();
p = NULL;
}
}
class UniquePointer
{
public:
void Dispose()
{
delete this;
}
protected:
UniquePointer() { }
UniquePointer(const UniquePointer&) { }
virtual ~UniquePointer() { }
};
Ответ 3
Это не разрешено, потому что если бы вы могли сделать следующее:
friend void SafeDispose(UniquePointer*& p)
{
p = new UniquePointer();
}
Building* building;
SafeDispose(building)
//building points to a UniquePointer not a Building.
Я думаю, что работа вокруг будет функцией шаблона.
Ответ 4
Чтобы ответить на заголовок вашего вопроса, вы не можете привязать неконстантную ссылку на базу к экземпляру производного класса, потому что тогда вы можете установить эту ссылку на указатель на базовый экземпляр, который не является производным. Рассмотрим эту функцию:
void Renew(UniquePointer *& p) {
delete p;
p = new UniquePointer();
}
если вы могли бы передать ему указатель на Building
, вы могли бы установить неверное указание на экземпляр UniquePointer
.
Как уже было предложено, решение заключается в изменении вашей ссылки на простой указатель. Это не только решает вашу проблему, но также улучшает реализацию SafeDispose()
; как вы написали, эта функция дала ложную идею о том, что вы всегда ставите 0 всех ваших экземпляров UniquePointer
. Но что могло бы произойти, если бы кто-нибудь написал (предполагая, что конструктор UniquePointer
был общедоступным для простоты):
UniquePointer *p1 = new UniquePointer();
UniquePointer *p2 = p1;
SafeDispose(p1);
Они ожидали, что все их UniquePointer
будут должным образом приняты во внимание, когда p2
фактически недействителен.
Ответ 5
Я думаю, что ваш SafeDispose, вероятно, будет больше похож:
friend void SafeDispose(UniquePointer** p) ...
Чтобы вызвать его с помощью
SafeDispose(&(UniquePointer*)b);
Тогда он должен работать таким образом.
Но ваше следующее утверждение
b->Dispose();
будет ломаться, потому что b теперь должен быть NULL, потому что он был удален и установлен в NULL с помощью вашего метода SafeDispose.