библиотеки / подходы к реализации объектных моделей на C ++

StackOverflow https://stackoverflow.com/questions/802706

  •  03-07-2019
  •  | 
  •  

Вопрос

Приношу извинения, если об этом спрашивали раньше, я не совсем уверен в терминологии или в том, как задать вопрос.

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

HTML-файлы Объектная модель документа (DOM) - это один из примеров;в качестве другого (надуманного) примера предположим, что у меня есть эти классы:

  • Entity
  • Person (подкласс Entity)
  • Couple (подкласс Entity)
  • Property
  • House (подкласс Property)
  • Pet (подкласс Property)
  • Car (подкласс Property)

и эти отношения:

  • Entity
    • имеет 1 home из класса House
    • имеет 0 или более pets из класса Pet
    • имеет 0 или более cars из класса Car
    • имеет 0 или более children из класса Person
  • Person
    • имеет 0 или 1 spouse из класса Person
    • имеет 0 или 1 marriage из класса Couple
    • имеет 0 или 1 parents из класса Entity (в этой модели родители не существуют, если их нет в живых!)
  • Couple
    • имеет 2 members из класса Person
  • Property
    • имеет 1 owner из класса Entity

Теперь, когда я продумал эти объекты и их взаимосвязи, я хочу начать создавать структуры данных, методы и поля для их обработки, и вот тут я теряюсь, поскольку мне приходится иметь дело с распределением памяти, управлением временем жизни и всем таким прочим.Вы можете столкнуться с проблемами, подобными следующим:Возможно, я захочу поместить объект в std::map или std::vector, но если я это сделаю, я не смогу хранить указатели на эти объекты, поскольку они могут быть перемещены, когда карта или вектор растут или сжимаются.

Один из подходов, который я использовал, когда много работал с COM, заключается в создании скрытой коллекции, которая содержала бы все.Каждый объект в коллекции имел уникальный идентификатор (либо число, либо имя), по которому его можно было найти в коллекции, и каждый объект имел указатель на коллекцию.Таким образом, если у вас есть объект, который хочет указывать на другой объект, вместо того, чтобы буквально удерживать указатель на другой объект, я сохраняю идентификатор и могу просмотреть его через скрытую коллекцию.Я могу использовать подсчет ссылок для автоматического решения проблем со сроком службы (за исключением случая непересекающихся циклов, иногда это не проблема).

Существуют ли другие подходы?Или существуют библиотеки, облегчающие такого рода работу в C ++?

Редактировать: тогда у вас возникают другие проблемы, такие как отношения между объектами, вероятно, во многих случаях будут изменяемыми, и вам нужно заранее подумать о том, как должны храниться ссылки на объекты и какие методы должны быть предоставлены для доступа к объектам друг от друга.Например, если у меня есть дескриптор для Person X, и я хочу представить концепцию "найти ребенка X по имени Джордж", тогда я должен сохранить имя "Джордж", а не номер ребенка:дочерние элементы могут храниться в векторе, и я могу вызвать X.getChildCount() и X.getChild(0), но "George" не всегда может быть дочерним номером 0, поскольку другие дочерние элементы могут быть вставлены перед "George" в дочернем векторе.Или у X может быть двое, трое или четверо других детей, которых также зовут "Джордж".Или "Джордж" может сменить свое имя на "Энтони" или "Джорджина".Во всех этих случаях, вероятно, лучше использовать какой-то уникальный неизменяемый идентификатор.

правка 2: (и я немного проясню свой вопрос, как только разберусь с этим) Я могу разобраться с выбором методов и имен свойств, я могу решить, использовать ли карту, список или вектор.Это довольно просто.Проблемы, с которыми я пытаюсь конкретно разобраться, заключаются в следующем:

  • как заставить один объект хранить ссылку на другой объект, когда эти объекты могут быть частью перераспределяемых структур данных
  • как работать с управлением временем жизни объекта, когда между объектами существуют взаимные связи
Это было полезно?

Решение

Вы писали о хранении объектов из вашей объектной модели внутри std::vector и т.д.и проблемы с использованием указателей на них.Это напоминает мне, что полезно разделить ваши классы C ++ на две категории (я не уверен в терминологии здесь):

  1. Классы сущностей которые представляют объекты, являющиеся частью вашей объектной модели.Они обычно полиморфны или потенциально будут полиморфными в будущем.Они создаются в куче и на них всегда ссылаются указатели или интеллектуальные указатели.Вы никогда не создаете их непосредственно в стеке, как члены класса / структуры, и не помещаете их непосредственно в контейнеры, такие как std::vectors.У них нет конструктора копирования или operator= (вы можете создать новую копию с помощью некоторого метода Clone).Вы можете сравнить их (их состояния), если это имеет для вас смысл, но они не взаимозаменяемы, потому что у них есть идентичность.Все два объекта различны.

  2. Классы значений которые реализуют примитивные пользовательские типы (такие как строки, комплексные числа, большие числа, интеллектуальные указатели, обертки дескрипторов и т.д.).Они создаются непосредственно в стеке или как члены класса / структуры.Их можно скопировать с помощью конструктора копирования и operator=.Они не являются полиморфными (полиморфизм и operator= плохо работают вместе).Вы часто помещаете их копии в контейнеры stl.Вы редко храните указатели на них в независимых местах.Они взаимозаменяемы.Когда два экземпляра имеют одинаковое значение, вы можете рассматривать их как одно и то же.(Однако переменные, которые их содержат, - это разные вещи.)

Есть много очень веских причин нарушить вышеуказанные правила.Но я заметил, что игнорирование их с самого начала приводит к тому, что программы становятся нечитаемыми, ненадежными (особенно когда дело доходит до управления памятью) и их трудно поддерживать.


Теперь вернемся к вашему вопросу.

Если вы хотите сохранить модель данных со сложными взаимосвязями и простым способом выполнения запросов типа "найдите ребенка X по имени Джордж", почему бы не рассмотреть какую-нибудь реляционную базу данных в памяти?

Обратите внимание, что когда вы собираетесь эффективно реализовать а) более сложные двунаправленные отношения и б) запросы, основанные на различных свойствах объекта, тогда вам, вероятно, потребуется создать индексированные структуры данных, которые очень похожи на то, что делает внутри реляционная база данных.Действительно ли ваши реализации (поскольку их будет много в одном проекте) будут более эффективными и надежными?

То же самое относится к "коллекциям всего" и идентификаторам объектов.Вам все равно нужно будет отслеживать связи между объектами, чтобы избежать идентификаторов без объектов.Чем это отличается от указателей?За исключением получения значимых ошибок вместо того, чтобы сходить с ума по всей памяти, то есть ;-)


Несколько идей по управлению памятью:

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

  • Кто-то уже предложил smart_ptr.Они великолепны и могут использоваться с контейнерами stl.Однако они основаны на ссылочном коутере, поэтому не создавайте циклы :-(.Я не знаю ни о каких широко используемых автоматических указателях c ++, которые могли бы обрабатывать циклы.

  • Возможно, существует какой-то объект верхнего уровня, которому принадлежат все остальные объекты.Например.часто вы можете сказать, что все фрагменты принадлежат документу, алгоритму или транзакции.Они могут быть созданы в контексте объекта верхнего уровня, а затем автоматически удалены при удалении их объекта верхнего уровня (когда вы удаляете документ из памяти или завершаете выполнение алгоритма).Конечно, вы не можете делиться фрагментами между объектами верхнего уровня.

Другие советы

Существует около миллиона (по самым скромным подсчетам) подходов к этому.Вы действительно спрашиваете "как мне разрабатывать программное обеспечение на C ++".И, боюсь, ответ таков: "что будет делать ваше программное обеспечение?" - простого знания того, что вы хотите иметь дело с людьми и домами, недостаточно.

Разве не в этом весь смысл ООП?То, о чем вы спрашиваете, является деталью реализации, которую вы скрываете за общедоступным интерфейсом этих классов, и поэтому вам не нужно беспокоиться, потому что вы можете изменить ее, не меняя интерфейс?Так что давай, попробуй сделать так, как ты предлагаешь.Затем, если возникнут проблемы с производительностью, памятью или другие проблемы, вы можете исправить реализацию, не нарушая остальную часть вашего кода.

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

Вы могли бы использовать boost::shared_ptr для решения проблем с памятью.Затем вы можете свободно копировать shared_ptr, возвращать его из функций, использовать как локальную переменную и т.д.

A Person тогда можно было бы иметь std::map< string, boost::shared_ptr<Person> >, так что X.getChild("George") было бы просто найти дочерний элемент на карте и вернуть указатель.Я думаю, вы поняли концепцию, поэтому остальное я оставлю вам в качестве упражнения ;)

Джейсон, мой любимый источник информации - это Книга часто задаваемых вопросов по C ++.Проблема в том, что вы фактически спрашиваете: "как я могу использовать C ++ для объектно-ориентированного программирования?"

Лучшее, что я могу сказать в ответе SO, это:

все эти вещи будут классами на C ++, а отношения и т.д. Будут очень похожи на языки сбора мусора, к которым вы привыкли:если вам нужна связь между Человеком и его Ребенком по имени "джордж", вы выбираете структуру данных, в которой могут храниться Люди или Дочерние элементы, проиндексированные по имени.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top