Возврат указателя аргумента на объект - PullRequest
0 голосов
/ 27 сентября 2010

В C ++ для Windows у меня есть фабрика объектов, которая должна создать серию объектов Info путем передачи указателя на объект в функцию Create и возврата созданного объекта.

void CreateInfoObject(AbstractInfo** info);  // The creation function 

AbstractInfoэто базовый класс, из которого у нас есть много типов объектов Info.

Я думал, что теперь мог бы создать объект Info следующим образом:

MyInfoObject* InfoObj = NULL;  // derived from AbstractInfo object
InfoFactory fc;

fc.CreateInfoObject(&InfoObj); // Now I want to get my initialized pointer back

Но он говорит, что не может выполнять приведение... Что случилось?

ОШИБКА: Невозможно привести из MyInfoObject ** _ W64 к AbstractInfo **

РЕДАКТИРОВАТЬ: первый ответ упоминает, что интерфейс ужасен, не может видеть, кто выделяет и т.д. ... Как я могу улучшить?

Ответы [ 5 ]

8 голосов
/ 27 сентября 2010

Давайте подумаем о возможной реализации CreateInfoObject:

void InfoFactory::CreateInfoObject(AbstractInfo** info)
{
  *info = new SuperInfo;
}

Теперь, SuperInfo и MyInfoObject не имеют ничего общего, верно?

Поэтому вообще запрещено следующее:

struct Base {};
struct D1: Base {};
struct D2: Base {};

int main(int argc, char* argv[])
{
  Base** base = nullptr;
  D1* d = nullptr;
  base = d;
}

Как это позволило бы D1 указать на что-то не связанное.

Существует несколько решений:

// 1. Simple
AbstractInfo* info = nullptr;
fc.CreateInfoObject(info);

// 2. Better interface
std::unique_ptr<AbstractInfo> info = fc.CreateInfoObject();

Тогда, если вы точно знаете, что у вас есть MyInfoObject, вы можете использовать:

MyInfoObject* myInfo = static_cast<MyInfoObject*>(info);

или если вы не уверены:

MyInfoObject* myInfo = dynamic_cast<MyInfoObject*>(info);

, который установит myInfo в nullptr, если когда-либо info не указывал на экземпляр MyInfoObject (или производный).

Имейте в виду, что ваш интерфейс действительно ужасен. Это очень секретно, и неясно, выделена ли память на самом деле или нет ... и кто отвечает за ее обработку, если она есть.

EDIT

В хорошем стиле C ++ мы используем RAII как для обозначения владения, так и для обеспечения очистки. RAII хорошо известен, хотя и не очень показателен, я сам предпочитаю новый SBRM (Scope Bound Resources Management).

Идея состоит в том, что вместо использования пустого указателя, который ничего не указывает на владение (то есть вы должны вызывать delete на нем?), Вы должны использовать умный указатель , как например unique_ptr.

Вы также можете использовать возвращаемый параметр метода, чтобы избежать двухэтапного процесса инициализации (сначала создайте указатель, а затем укажите его на объект). Вот краткий пример:

typedef std::unique_ptr<AbstractInfo> AbstractInfoPtr;

// Note: if you know it returns a MyInfoObject
// you might as well return std::unique_ptr<MyInfoObject>
AbstractInfoPtr InfoFactory::CreateInfoObject()
{
  return AbstractInfoPtr(new MyInfoObject());
}

// Usage:
int main(int argc, char* argv[])
{
  InfoFactory factory;
  AbstractInfoPtr info = factory.CreateInfoObject();

  // do something

} // info goes out of scope, calling `delete` on its pointee

Здесь нет никакой двусмысленности в отношении собственности.

Также обратите внимание, как вы лучше понимаете свой вопрос здесь:

  std::unique_ptr<MyInfoObject> info = factory.CreateInfoObject();

не будет компилироваться, потому что вы не можете преобразовать AbstractInfo* в MyInfoObject* без использования static_cast или dynamic_cast.

3 голосов
/ 27 сентября 2010

Компилятор сказал вам, что не так. Вы не можете преобразовать указатель типа T * в указатель типа U *, когда T и U не имеют ничего общего друг с другом. Здесь T = MyInfoObject *, U = AbstractInfo *, и это два разных типа указателей, которые не разделяют никаких отношений наследования.

3 голосов
/ 27 сентября 2010

Поскольку CreateInfoObject() принимает указатель на указатель на AbstractInfo, функция может вернуть экземпляр AbstractInfo, то есть , а не экземпляр MyInfoObject. Таким образом, вы можете получить указатель на MyInfoObject, который на самом деле указывает на DifferentInfoObject.

Измените MyInfoObject *InfoObj на AbstractInfo *InfoObj, и оно должно работать. Не отбрасывайте преобразование ни с чем, кроме dynamic_cast<>, потому что вы точно не знаете, что CreateInfoObject() возвращает экземпляр этого подкласса.

2 голосов
/ 27 сентября 2010

Указатель на указатель не так гибок, как указатель на объект.Компилятор будет строго применять тип без учета дерева наследования.

Самый безопасный способ исправить этот код - двойное присвоение:

MyInfoObject* InfoObj = NULL;  // derived from AbstractInfo object 
AbstractInfo* temp = NULL;
InfoFactory fc; 

fc.CreateInfoObject(&temp); 
InfoObj = dynamic_cast<MyInfoObject*>(temp);
0 голосов
/ 27 сентября 2010

Рассмотрим, что происходит в CreateInfoObject.

Допустим, есть еще один подкласс AbstractInfo, звоните Foo.

Внутри CreateInfoObject мы создаем новый Foo и присваиваем ему *info. (Разрешено upcast).

Но снаружи у нас теперь есть Foo внутри MyInfoObject**, что неправильно.

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