Чтобы прояснить обсуждение, я собираюсь описать проблему в очень общем виде, то есть я не буду ни давать имена реальных классов, ни описывать домен / контекст (однако я мог бы, если это окажется срочно).
Представьте себе класс A
, Пусть этот класс имеет 2 неизменный поля, например x
а также y
(пожалуйста, обратите внимание, что это может быть потенциально большой объекты, т.е. неэффективно копировать). Кроме того, пусть эти x
а также y
быть первичный поля, т.е. только они используются при реализации ==
/!=
операторы, а также хэш-вычислительная функция.
поскольку A
является неизменный с точки зрения x
а также y
идея состоит в том, чтобы позволить несколько экземпляров A
(сказать a1
а также a2
) который имеет a1.x == a2.x
a1.y == a2.y
(Т.е. a1 == a2
) чтобы косвенным образом имеют общий доступ к этим x
а также y
, чтобы не было ненужного дублирования.
Более того, теперь представьте, что есть еще одно поле в A
: z
, который вторичный а также изменчивый, и служит своего рода настройкой поведения для A
, По замыслу желательно сделать это поле общим для равный случаи A
тоже. Итак, если я призываю a1.setZ(...)
это изменение также повлияет a2
потому что их доступ к z
является общий.
В результате мы получаем класс A
который имеет чистый семантика значения, но разделяет его членов косвенным образом через равные случаи. AFAIK такая картина называется легкий вес или же наложения спектров.
Еще одна деталь, прежде чем мы перейдем к вопросу. Большинство классов в проекте реализованы с использованием Pimpl идиома:
private:
class Private;
Private* p;
и класс A
не является исключением. Вот почему предложенная идея реализации описанной выше схемы заключается в следующем.
A::Private
вместо сырого вA::Private
;A
проверить, является ли общийA::Private
уже существует в набореx
а также y
конечно) и если да, то просто установите p
A::Private
и хранитьp
к этому;A::Private
деструктор должен удалить общий указатель в this
из набора.Это выглядит как самая простая и интуитивно понятная реализация. Однако проблема заключается в том, что, поскольку этот глобальный набор содержит общий указатель в A::Private
это означает, что даже когда все A
уничтожены, счетчик ссылок останется на 1
т.е. оно никогда не достигнет 0
и, таким образом, память никогда не освобождается.
Я думал, что было бы хорошо, если бы некоторые общие указатели предложил бы метод для установки нижней границы для счетчика ссылок. В этом случае, например, я бы просто установить его 1
что означало бы, что когда оно достигнет 1
это освобождает память. К сожалению, я не нашел никакой реализации такого поведения в популярных библиотеках (Boost, Qt, Poco и т. Д.). Конечно, я мог бы сделать ручной подсчет ссылок для моей проблемы, но это просто не правильно и пахнет как изобретать колесо.
Возможно, есть и другие способы решения этой проблемы. Ждем ваших предложений.
НОТА: Я хотел бы немедленно перехватить любой совет, чтобы преобразовать проблему в семантика указателя о котором я хорошо знаю. Мне нужно решение именно по схеме, описанной выше.
Если бы я правильно понял, в чем проблема вашего дизайна, то я бы позволил глобальному набору содержать слабый, не владеющие указателями (например, weak_ptr<>
), которые могут проверить, если они висят, но они не увеличивают количество ссылок.
std::vector<std::weak_ptr<Private>> _objects;
Поэтому, когда все владеющие общими указателями на объект уничтожаются, объект будет уничтожен также **.
Теперь ваш глобальный сет останется висящим weak_ptr<>
, но приятно то, что вы можете проверять указывает ли этот указатель на живой объект или нет (используйте lock
() функция-член для получения возможно нулевого shared_ptr<>
, И если этого не произойдет, вы не будете разыменовывать это:
// A simple, hypothetical loop through the collection of objects
// which does something, but checks whether the pointers are
// dangling before doing that something on a possibly dead object
// that would be Undefined Behavior)
std::for_each(_objects.begin(), _objecs.end(), [] (std::weak_ptr<Private> p)
{
std::shared_ptr<Private> sp = p.lock();
if (sp != nullptr)
{
sp->callMember(); // For instance...
}
});
Если вы также хотите удалить соответствующий weak_ptr<>
для объекта из коллекции, как только объект будет уничтожен, то вы можете использовать пользовательский удалитель рутина. Ваша процедура будет вызвана, когда объект будет уничтожен, и будет передан указатель на этот объект: в этот момент, перед освобождением, вы можете стереть соответствующий элемент из набора.
Например, функция, которая создает новые объекты типа A
и возвращает shared_ptr
чтобы это могло выглядеть так:
static std::shared_ptr<object> make_A()
{
std::shared_ptr<Private> sp(
new Private(), // Instantiate the object
[] (Private* p) // Set up the custom deleter...
{
// Remove the corresponding element from the vector...
_objects.erase(
// ...so let's find that element!
std::find_if(
_objects.begin(),
_objects.end(),
[p] (std::weak_ptr<priv> wp)
{
// lock() will return a null pointer if wp is dangling
std::shared_ptr<priv> sp = wp.lock();
// In case wp is not dangling, return true if and only
// if it points to the object we're about to delete
return ((sp != nullptr) && (sp.get() == p));
})
);
});
}
Здесь я предположил C ++ 11, вы можете легко сделать то же самое в C ++ 03, заменив std::shared_ptr<>
с boost::shared_ptr<>
, std::weak_ptr<>
с boost::weak_ptr<>
и лямбды с правильно определенными функторами.
Надеюсь это поможет.
Вы проверили Boost.Flyweight из?