Как сохранить инкапсуляцию, когда смерть одного объекта делает больным другой объект

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

#include <iostream>

const int my_int = 5;

class A {
private:
int n_;
public:
int n() const { return n_; }
A(int);
};

A::A(int n__) : n_(n__) {}

class B {
private:
const A *ap_;
public:
int m() const { return 1 + ap_->n(); }
explicit B(const A *);
};

B::B(const A *const ap__) : ap_(ap__) {}

int main()
{
std::cout << "Will put an unnamed A on the heap.\n";
A *const p = new A(my_int);
std::cout << "Have put an unnamed A on the heap.\n";
std::cout << "p->n() == " << p->n() << "\n";
B b(p);
std::cout << "b. m() == " << b. m() << "\n";
std::cout << "Will delete  the unnamed A from the heap.\n";
delete p;
std::cout << "Have deleted the unnamed A from the heap.\n";
std::cout << "b. m() == " << b. m() << "\n"; // error
return 0;
}

Конечно, это можно исправить, дав b хранить копию a а не указатель на него, но предположим, что b предпочитает не хранить копию (потому что a занимает много памяти или по какой-то другой причине). Предположим, что b предпочитает просто ссылаться на существующие a, когда a тихо умирает, b никогда не замечает Тогда, когда b пытается использовать aнепредсказуемые результаты поведения.

На моем компьютере вывод примера выглядит так:

Will put an unnamed A on the heap.
Have put an unnamed A on the heap.
p->n() == 5
b. m() == 6
Will delete  the unnamed A from the heap.
Have deleted the unnamed A from the heap.
b. m() == 1

Однако на вашем компьютере результатом может быть ошибка или кто знает, что.

Проблема моего примера заключается в том, что этот пример косвенно нарушает инкапсуляцию bоставив программисту помнить, что b зависит от продолжающегося существования a, Когда программист забывает, программа ломается. Притесненный программист при этом обязан сохранять b в виду, когда он работает над a хотя тип A как таковой не заботится о типе B. Как вы знаете, объектно-ориентированные программисты предпочитают не иметь в виду такие мелочи, если могут помочь.

Я сталкиваюсь с этой проблемой под более сложным видом время от времени во время программирования. Я встретил это снова сегодня. Чувствуется, что каким-то образом должен существовать элегантный шаблон проектирования для поддержания правильной инкапсуляции bперенести с программиста на компилятор ответственность запоминания bзависимость от aсуществование — и что шаблон должен включать в себя нечто менее сложное, чем умные указатели и полномасштабный подсчет ссылок. Однако, возможно, я ошибаюсь. Возможно, это именно то, для чего нужны умные указатели для подсчета ссылок. В любом случае, я не знаю ни правильного шаблона для решения проблемы, ни лучшего способа исправить код.

Если бы вы знали, расскажите об этом?

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

(Мой компилятор до сих пор не очень хорошо поддерживает C ++ 11, но если C ++ 11 предоставляет функцию, специально предназначенную для решения моей проблемы, то, конечно, мне должно быть интересно узнать об этом. Правда, мой вопрос в основном касается основ ОО / области видимости. Вопрос еще больше интересует базовый шаблон проектирования, чем та или иная новая функция последнего компилятора.)

ПРИМЕЧАНИЕ ДЛЯ ЧИТАТЕЛЯ

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

Тем не менее, именно комбинация двух ответов лучше всего отвечает на этот вопрос. Вы должны прочитать оба:

  • @ MatthieuM. ‘S ответ перераспределение собственности и модели наблюдателей; а также
  • @ JamesKanze-х ответ почему и когда модель наблюдателя может быть предпочтительнее.

1

Решение

Я могу придумать два конкретных решения:

  • совместная собственность
  • схема наблюдателя

В решении с общим владением время жизни a определяется счетчиком количества владельцы, только когда нет больше владельца aНа этом жизнь кончается. Это обычно реализуется с использованием std::shared_ptr<A>,

В решении наблюдателя, когда a передается b, a запоминает, что b содержит ссылку на это. Тогда, когда a умирает, либо уведомляет b сразу или оставить «жетон» для b быть уведомленным при следующей попытке доступа.

Прямое уведомление обычно обрабатывается путем ведения списка текущих рефереров и вызова каждого из них во время уничтожения (чтобы они могли стереть свою ссылку). Это типичная картина наблюдателя.

Непрямое уведомление обычно обрабатывается наличием прокси-объекта, через который можно перейти к a, когда a умирает прокси уведомляется (O (1)) и когда b попытки доступа a он должен пройти через прокси. Существуют различные трудности в реализации этого самостоятельно, поэтому лучше использовать стандартные средства: std::shared_ptr на этот раз в сочетании с std::weak_ptr,

Наконец, эти решения не эквивалентны.

  • долевая собственность подразумевает, что a не может умереть, пока b живет в то время как схема наблюдателя позволяет a умереть первым
  • прямое уведомление позволяет b реагировать немедленно о aсмерть, но в то же время может сделать программу более хрупкой (во время aдеструктор исполнения, вы не должны бросать никаких исключений)

Выбери свой яд 🙂

3

Другие решения

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

Конечно, гораздо чаще причина, по которой объект
удален, потому что логика программы требует этого, и отсрочка
удаление с помощью какого-то умного указателя сломается
логика программы. В таких случаях класс, имеющий указатель на
объект должен быть уведомлен о том, что объект был удален,
действовать в результате. В этом случае картина наблюдателя является
стандартное решение; ваш объект b будет зарегистрирован в A
возражать, быть информированным о его кончине, и делать то, что должен был сделать
в результате. Если мы возьмем простой случай, когда ваш B учебный класс
является Sessionи A класс Connection, Если фактический
разрывается соединение, Connection класс будет проинформирован, и
саморазрушение. При этом (в своем деструкторе) он будет
сообщить все объекты, зарегистрированные в нем. Если вы на
сервер, Session класс, вероятно, войдет в проблему и
самоуничтожение — разорванное соединение прекращает
сессия. В клиенте, однако, Session класс может попытаться
создать новый Connectionи прервать сеанс, только если
выходит из строя.

4

Умное решение — использовать умные указатели, если вам действительно нужно хранить только один объект, а не несколько копий!

C ++ 11 дает вам множественный выбор выбирать из:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

В вашем случае, я думаю, вам нужен второй: std::shared_ptr,

3

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

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

Как сказал Наваз, реальный способ перестать беспокоиться об этом — использовать умные указатели с правильной семантикой владения. Если вы хотите передать право собственности на другой объект, используйте std::unique_ptr, Если вы хотите поделиться собственностью, используйте std::shared_ptr, Если вы хотите использовать необработанный необработанный указатель, я не буду говорить не, но, по крайней мере, знайте, какие проблемы это может вызвать.


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

const std::unique_ptr<A> p(new A(my_int));
B b(p.get());
// the A object will be destroyed at the end of the current scope

Здесь у вас нет (очень минимум!) Накладных расходов на std::shared_ptr, Я не уверен, что действительно рекомендую это. Гораздо предпочтительнее быть полностью умным, чем полуумным. const std::unique_ptr имеет семантику владения похожую на boost::scoped_ptr: «У меня есть это, и никто другой никогда не будет». Разница в том, что вы не сможете позвонить reset на этом const std::unique_ptr как вы бы на boost::scoped_ptr,

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector