Преобразование из производного * в базу * & - PullRequest
6 голосов
/ 10 апреля 2009

Я пытался ответить на упомянутый вопрос здесь , передавая ссылку на указатель вместо указателя на указатель следующим образом:

class Parent 
{
};

class Child : public Parent 
{
};

void RemoveObj(Parent*& pObj)
{
    delete pObj;
    pObj = NULL;
}

int main()
{
    Parent* pPObj = new Parent;
    Child*  pCObj = new Child;
    pPObj = new Parent();
    pCObj = new Child();



    RemoveObj(pPObj);
    RemoveObj(pCObj); // This is line 32
    return 1;
}

Но это приводит к следующей ошибке компилятора в строке 32:

ошибка C2664: «RemoveObj»: невозможно преобразовать параметр 1 из 'Child *' в 'Родитель * &'

Я согласен с тем, что преобразование из дочернего ** в родительское ** не допускается. Но почему это преобразование также не разрешено?

Ответы [ 5 ]

7 голосов
/ 10 апреля 2009

Объект типа Child* не может быть привязан к Parent*& по той же причине, по которой Child** не может быть преобразован в Parent**. Разрешение этого позволит программисту (намеренно или нет) нарушать безопасность типов без приведения.

class Animal {};

class DangerousShark : public Animal {};

class CuteKitten : public Animal {};

void f(Animal*& animalPtrRef, Animal* anotherAnimalPtr)
{
    animalPtrRef = anotherAnimalPtr;
}

void g()
{
    DangerousShark myPet;
    CuteKitten* harmlessPetPtr;

    f(harmlessPetPtr, &myPet); // Fortunately, an illegal function call.
}

Редактировать

Я думаю, что некоторая путаница возникает из-за свободного употребления слов «преобразовать» и «преобразование».

Ссылки не могут быть восстановлены, в отличие от объектов, которые могут быть переназначены, поэтому в контексте ссылок, когда мы говорим о преобразовании, нас может беспокоить только инициализация новой ссылки.

Ссылки всегда связаны с объектом, и из вопроса ОП было ясно, что он стремится получить ссылку, которая является прямой связью с существующим объектом. Это допустимо только в том случае, если объект, использованный для инициализации ссылки, совместим со ссылкой с типом ссылки. По сути, это только в том случае, если типы одинаковы, или тип объекта определяется на основе типа ссылки, а тип ссылки по меньшей мере такой же квалифицированный как cv, что и объект инициализации. В частности, указатели на разные типы не являются ссылочно-совместимыми, независимо от отношения указанных типов.

В других случаях ссылка может быть инициализирована чем-то, что может быть преобразовано в тип ссылки. В этих случаях, однако, ссылка должна быть постоянной и не изменчивой, и преобразование создаст временный объект, и ссылка будет привязана к этому временному, а не к исходному объекту. Как уже отмечалось, это не подходит для требований мотивирующего примера ОП.

Таким образом, Child может быть напрямую связан с Parent&, но Child* не может быть напрямую связан с Parent*&. Parent* const& можно инициализировать с помощью Child*, но ссылка на самом деле будет привязана к временному Parent* объекту, инициализированному копией из Child* объекта.

4 голосов
/ 10 апреля 2009
  • У ваших классов нет виртуальной функции. См. FAQ 20.7

  • Beacuse Parent *& является ссылкой на указатель на Parent объект. Вы передаете указатель на Child - это несовместимые типы. Вы можете привязать временную ссылку к const-ссылке, т.е. если вы измените свой параметр на:

    void RemoveObj(Parent* const& foo);

Но тогда вы не сможете много с этим сделать.

Это был просто тестовый код, поэтому я не создавал виртуальных деструкторов. Если я правильно понимаю во втором вызове RemoveObj (), я получаю временный объект Parent *, который можно передать как постоянную ссылку на функцию. Это правильно?

Я настоятельно рекомендую вам запустить следующую программу в стандартном режиме C ++ 98, один раз как в, а затем снова после того, как вы закомментировали foo(b) и раскомментировали delete b. Затем попробуйте ввести virtual до ~s(). Различия должны быть очевидными!

#include <iostream>
using namespace std;
struct s { 
  s() {cout << __func__ << endl; }
  ~s() {cout << __func__ << endl; } 
};

struct t : s { 
   t() {cout << __func__ << endl; }
   ~t() {cout << __func__ << endl; } 
};

void foo(s* const& x) { delete x; }

int main() {
 t* b = new t;
 foo(b);
 //delete b;
}
1 голос
/ 10 апреля 2009

Вы можете конвертировать Child* to a Parent*: это создает временный. Но вы не можете привязать неконстантную ссылку к этому временному.

Это не проблема **/*&/etc. То, что вы пытаетесь сделать, вполне нормально и имеет смысл. То, что Акула против Котенка имеет ту же самую проблему: не вопрос смешивания котенка и акул. Вы не можете привязать неконстантную ссылку к этому безымянному указателю.

Это не проблема Parent** vs. Child**: там, если Child** было Parent**, тогда можно было бы присвоить p[0] = new NotAChild;. Коллекция объектов, которые все являются подтипами A, не является коллекцией A.

0 голосов
/ 10 апреля 2009

Это не сработает по причинам, указанным Дирком. Если вам действительно нужен метод RemoveObj, я бы просто сохранил ваш недавно выделенный дочерний объект как родительский *:

#include <iostream>

class Parent 
{
public:
    virtual ~Parent()
    {
        std::cout << "Parent destructor" << std::endl;
    }
};

class Child : public Parent 
{
public:
    virtual ~Child() 
    {
        std::cout << "Child destructor" << std::endl;
    }
};

void RemoveObj(Parent*& pObj)
{
    delete pObj;
    pObj = NULL;
}



int main (int argc, const char * argv[]) {

    Parent* pPObj = new Parent;
    Parent*  pCObj = new Child;

    RemoveObj(pPObj);    
    RemoveObj(pCObj); // This is line 32


    return 0;
}
0 голосов
/ 10 апреля 2009

type * & - это еще одна синтаксическая форма типа ** и Parent * & and Child *, которые не связаны друг с другом, а также с Parent ** и Child ** - это разные типы не в одной иерархии классов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...