Возврат абстрактных типов данных в C ++ без висячих указателей - PullRequest
5 голосов
/ 19 апреля 2011

Алло,

Я из C # и у меня мало опыта в C ++. Чтобы создать чистый код, я пытаюсь разделить реализацию и интерфейсы и использовать наследование, когда это возможно. И когда я попытался применить типичные концепции C # к C ++, я столкнулся с проблемой, которую до сих пор не смог решить. Я предполагаю, что это, вероятно, тривиально для опытного программиста C ++, но это уже давно сводило меня с ума.

Сначала я объявляю базовый класс (в данный момент он не содержит никакой логики, но будет в будущем)

class PropertyBase : public IProperty
{
};

Затем я определяю интерфейс для свойств

class IProperty
{
public:
    virtual ~IProperty() {};
    virtual PropertyBase    correct(const ICorrector &corrector) = 0;
    virtual PropertyBase    joinWith(const PropertyBase &partner, const IRecombinator &recombinator) = 0;
};

Вот в чем проблема: компилятор возвращает ошибки для двух виртуальных функций, говоря, что не разрешается объявлять функцию, которая возвращает абстрактный класс. Конечно, я не хочу возвращать объект типа PropertyBase. Я хочу объявить другие классы, которые наследуются от PropertyBase, которые возвращают экземпляр себя.

Теперь я прочитал, что возможный способ обойти это модифицировать IProperty так, чтобы он возвращал указатели:

class IProperty
{
public:
    virtual ~IProperty() {};
    virtual PropertyBase*   correct(const ICorrector &corrector) = 0;
    virtual PropertyBase*   joinWith(const PropertyBase &partner, const IRecombinator &recombinator) = 0;
};

Однако я хотел бы избежать этого, если это возможно, чтобы предотвратить утечки памяти. Было бы здорово, если бы у кого-то была лучшая идея для решения этой проблемы.

Большое спасибо

Ответы [ 5 ]

5 голосов
/ 19 апреля 2011

Если вы боитесь утечек памяти, переключитесь на умные указатели. Это имеет дополнительное преимущество, заключающееся в самодокументировании по отношению к. право собственности на возвращенный объект.

class IProperty
{
public:
    virtual ~IProperty() {};
    virtual std::unique_ptr<PropertyBase> correct(const ICorrector &) = 0;
    virtual std::unique_ptr<PropertyBase> joinWith(const PropertyBase &,
                                                   const IRecombinator &) = 0;
};

В вашем коде клиента:

std::unique_ptr<PropertyBase> pb(property.correct(corrector));
// use pb and forget about it; smart pointers do their own cleanup

Или, если вы хотите подсчитывать ссылки на объект:

std::shared_ptr<PropertyBase> pb(property.correct(corrector));

См. Документы MSDN для unique_ptr, shared_ptr.

2 голосов
/ 19 апреля 2011

Возможно, это не тот ответ, который вы ищете, но мне кажется, что вы немного запутались в отношении указателей и значений в C ++.

Вы должны возвращать либо указатель, либо ссылку в C ++, если вам нужен правильный ad-hoc полиморфизм. В этом случае компилятор выдал ошибку, потому что базовый класс был абстрактным. Если бы создание экземпляра абстрактного класса было бы возможным, в нем были бы «дыры».

Правило большого пальца: всякий раз, когда у вас есть иерархия классов, никогда не возвращайте объекты таких типов по значению. Предположим, у вас есть class Base { int x; } и class Derived : public Base { int y; }. Если вы сделаете это:

Base Function() { Derived d; return d; }
...
Base b = Function();

Тогда b не будет значением класса Derived, "скрывающимся" за Base. Значение b БУДЕТ Base. Компилятор "отрежет" различия между Derived и Base и поместит их в b.

В C ++ вам придется использовать указатели или ссылки для облегчения специального полиморфизма. Ссылки в C # - это почти то же самое, что и указатели в C ++, за исключением того, что вам не нужно освобождать объекты в C #, поскольку сборщик мусора справится с этим за вас.

0 голосов
/ 20 апреля 2011

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

Например:

class A : public Base
{
public:
   Base *correct(const I &c) 
     { p2 = do_something(c); return &p2; }
   ...
private:
   A2 p2;
};

Что делает эту работу тем, что вы храните p2 внутри класса и никогда не передаете права собственности на объекты извне. Функция в интерфейсе не создает новые объекты, а вместо этого просто возвращает существующий, настроенный на исправление состояния на основе параметров функции. Это хорошая альтернатива решению unique_ptr и shared_ptr, которое опирается на выделение кучи, создание новых объектов и их передачу.

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

class B : public Base
{
public:
   Base *correct(const I &c) {
      switch(c.get_bool()) {
         case false: p3 = do_something_else(c); return &p3;
         case true: p4 = do_something(c); return &p4;
      };
   }
 private:
    B3 p3;
    B4 p4;
};

Но размещение ваших объектов p3 и p4 внутри текущего объекта B решит эту проблему полностью.

0 голосов
/ 19 апреля 2011

В целом, если вы собираетесь выполнять серьезный объем работы в C ++, очень важно освоиться с указателями и управлением памятью. Для всестороннего освещения этого и других сложных аспектов C ++ я настоятельно рекомендую книги Effective C ++ Скотта Мейерса и книги Exceptional C ++ Херба Саттера.

0 голосов
/ 19 апреля 2011

Нет ничего плохого в возвращении указателя на объект.Если вас беспокоят утечки памяти, как и должно быть, решение заключается в использовании интеллектуальных указателей для хранения возвращенного указателя.Наиболее гибким из них является shared_ptr из boost или новый стандарт C ++ 0x.

...