Вопрос, связанный с дизайном C ++ - PullRequest
4 голосов
/ 25 апреля 2010

Вот сюжет вопроса: предположим, у меня есть некоторые абстрактные классы для объектов, назовем его Object. Это определение будет включать в себя 2D положение и размеры. Пусть у него также есть метод virtual void Render(Backend& backend) const = 0, используемый для рендеринга.

Теперь я специализируюсь на своем дереве наследования и добавляю классы Rectangle и Ellipse. Думаю, у них не будет своих свойств, но у них будет свой virtual void Render метод. Допустим, я реализовал эти методы, так что Render для Rectangle фактически рисует некоторый прямоугольник, и то же самое для эллипса.

Теперь я добавляю некоторый объект с именем Plane, который определяется как class Plane : public Rectangle и имеет закрытый член std::vector<Object*> plane_objects;

Сразу после этого я добавляю метод для добавления какого-либо объекта в мою плоскость.

И тут возникает вопрос. Если я спроектирую этот метод как void AddObject(Object& object), я столкнусь с проблемой, так как не смогу вызывать виртуальные функции, потому что я бы сделать что-то вроде plane_objects.push_back(new Object(object));, и это должно быть push_back(new Rectangle(object)) для прямоугольников и new Circle(...) для кругов.

Если я реализую этот метод как void AddObject(Object* object), он выглядит хорошо, но затем где-то еще это означает выполнение вызова, как plane.AddObject(new Rectangle(params));, и это обычно беспорядок, потому что тогда неясно, какая часть моей программы должна освободить выделенную память ,

["при уничтожении самолета? Почему? Мы уверены, что вызовы AddObject были сделаны только как AddObject(new something).]

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

Есть идеи?

Ответы [ 5 ]

3 голосов
/ 25 апреля 2010

Ваша реальная проблема - управление временем жизни объектов . На ум приходят четыре возможности:

  1. Ваш контейнер (т. Е. Plane) предполагает владение всеми содержащимися объектами и, следовательно, delete присваивает их, как только он сам будет уничтожен.

  2. Ваш контейнер (Plane) не принимает на себя права собственности, и тот, кто добавил объекты в ваш контейнер, будет нести ответственность за их уничтожение.

  3. Время жизни ваших объектов управляется автоматически.

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

То, что у вас сейчас есть, похоже на подход № 4. Делая:

plane_objects.push_back(new Object(object));

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


Опции # 1 и # 2 легко реализовать, потому что они определяют контракт, которому просто должна следовать ваша реализация. Вариант № 3 требует, например, умные указатели или другое решение, включающее подсчет ссылок.

Если вы хотите продолжать придерживаться варианта № 4, вы можете, например, расширить класс Object с помощью метода clone, чтобы он возвращал правильный тип объекта. Это избавит от неправильного new Object(...).

class Object
{
    public:
        virtual Object* clone() const = 0;
        ...
};

...

Object* Rectangle::clone() const
{
    return new Rectangle(*this);  // e.g. use copy c'tor to return a clone
}

P.S.: Обратите внимание, как контейнеры STL решают эту проблему: допустим, вы объявляете vector<Foo>. Этот вектор будет содержать копии вставленных в него объектов (вариант № 4 в моем ответе). Однако, если вы объявите коллекцию как vector<Foo*>, она будет содержать ссылки на исходные объекты, но не будет управлять их временем жизни (опция # 2 в моем ответе).

3 голосов
/ 25 апреля 2010

Используйте умный указатель, например boost::shared_ptr или boost::intrusive_ptr.

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

1 голос
/ 25 апреля 2010

Сначала вам нужно выяснить вашу семантику идентификации и владения объектом:

Является ли каждый объект реальной идентичностью или он просто относится к реальной сущности? В первом случае вы не можете использовать clone () и вам нужно использовать подсчет ссылок (через boost :: shared_ptr) или передавать по ссылке, если владелец экземпляра гарантированно переживет ссылки на него. В последнем случае clone () может иметь больше смысла, и вы можете спокойно игнорировать проблемы владения.

В общем, вам следует избегать передачи голых указателей и использовать вместо него boost :: shared_ptr или ссылки.

1 голос
/ 25 апреля 2010

при уничтожении самолета?Зачем?Мы уверены, что вызовы AddObject были сделаны только как AddObject (что-то новое).

Невозможно быть уверенным.Но что-то должно управлять временем жизни объекта.Вы должны просто четко задокументировать, что это за вещь.

1 голос
/ 25 апреля 2010

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

class Object {
public:
  virtual Object clone() = 0;
};

class Rectangle : public Object {
public:
  virtual Rectangle clone() { /* create and return an identical Rectangle */ }
};

class Ellipse : public Object {
public:
  virtual Ellipse clone() { /* create and return an identical Ellipse */ }
};

class Plane : public Rectangle {
  std::vector<Object*> plane_objects;
public:
  virtual Plane clone() { /* create and return an identical Plane */ }
  void AddObject(const Object& object) {
    plane_objects.push_back(object.clone());
  }
};

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

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