Подтвердить что ты не робот

Почему указатель на производный класс не может быть передан функции, ожидающей ссылки на указатель на базовый класс?

Извините за длинный заголовок, но я хочу быть конкретным. Я ожидал, что следующий код будет работать, но это не так, и я не могу понять, почему:/

#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;
}
4b9b3361

Ответ 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.