Могу ли я использовать shared_ptrs в этом примере?

У меня есть простая система обработки событий, которая вызывает у меня проблемы. Чтобы использовать его, я наследую от класса EventHandler. Затем конструктор регистрирует каждый объект при построении.

Вот конструктор EventHandler:

EventHandler::EventHandler()
{
   EventDispatcher::getInstance().registerListener(this);
}

Это вызывает функцию-член registerListener() EventDispatcher, которая сохраняет это в векторе.

void EventDispatcher::registerListener(EventHandler* listener) 
{
   mListenerList.push_back(listener);
}

Где mLisernerList выглядит так:

vector<EventHandler*> mListenerList;

EventDispatcher просто вызывает sendEvent() для каждого элемента вектора, чтобы уведомить его о событии.

Позвольте мне привести пример, чтобы продемонстрировать мою проблему. Допустим, мой класс Buttons наследуется от EventHandler. Я создам объекты-кнопки в куче, а затем размещу интеллектуальные указатели на все мои кнопки в одном векторе.

vector<unique_ptr<Buttons>> mButtons;   
mButtons.push_back(unique_ptr<Buttons>(new Button()));

Что происходит, так это то, что я получаю вектор unique_ptrs в mButtons и вектор необработанных указателей в mListenerList, указывающих на одни и те же динамически выделяемые объекты Button. Я не хочу, чтобы умные и необработанные указатели указывали на один и тот же объект.

В идеале я хотел бы, чтобы вектор shared_ptrs в mButtons и вектор weak_ptrs в mListenerList указывал на динамически выделяемые объекты Button, позволяя EventHandler регистрировать каждый объект при создании. Это возможно?


person user870130    schedule 06.05.2014    source источник
comment
Это возможно. Вы можете заглянуть в std::enable_shared_from_this   -  person OmnipotentEntity    schedule 06.05.2014
comment
@OmnipotentEntity Я понимаю, что мне нужно будет использовать enable_shared_from_this, чтобы избежать проблем с этим. Хотя это может быть более тонкой проблемой, я просто в тупике, сохраняя текущее поведение EventHandler, но расширяя его, чтобы поделиться указателем.   -  person user870130    schedule 06.05.2014
comment
Что не так с умными и необработанными указателями, указывающими на один и тот же объект? Пока интеллектуальные указатели владеют, а необработанные указатели - нет.   -  person Chris Drew    schedule 06.05.2014
comment
@Chris Drew Я следую этому совету: если вы хотите получить все преимущества интеллектуальных указателей, ваш код должен избегать использования необработанных указателей для ссылки на одни и те же объекты; в противном случае слишком легко иметь проблемы с оборванными указателями или двойным удалением... наличие некоторых встроенных указателей в миксе может быть полезным, но только если вы очень осторожны, чтобы их нельзя было использовать, если их объекты были удалены , и вы никогда, никогда не будете использовать их для удаления объекта или иным образом осуществлять с ними право собственности на объект. Я рекомендую... никогда не смешивать их.   -  person user870130    schedule 06.05.2014
comment
@ user870130: ИМХО, это плохой совет. Я думаю, что общепринятое мнение таких гуру, как Херб Саттер, заключается в том, что умные указатели отлично подходит для владения указателями (или, возможно, владения в случае weak_ptr), но необработанные указатели по-прежнему лучше всего подходят для указателей, не владеющих, когда вы знаете, что объект переживет указатель. Интеллектуальные указатели не являются серебряной пулей, вам все равно нужно четкое представление о владении, иначе вы получите проблемы, возможно, не оборванные указатели или двойные удаления, а, например, низкую производительность или утечки памяти.   -  person Chris Drew    schedule 07.05.2014


Ответы (2)


class EventHandler {
private
    EventHandler(); //make the constructors protected, also in derived when possible

    template<class T, class...Us> //and make this function a friend
    friend std::shared_ptr<EventHandler> make_event(Us...us);
};
//this is the function you use to construct Event objects
template<class T, class...Us>
std::shared_ptr<T> make_event(Us...us)
{
    auto s = std::make_shared<T>(std::forward<Us>(us)...);
    EventDispatcher::getInstance().registerListener(s);
    return s;
}

Это вызывает функцию-член registerListener() EventDispatcher, которая сохраняет это в векторе.

void EventDispatcher::registerListener(std::weak_ptr<EventHandler> listener) 
{
   mListenerList.push_back(listener);
}

Где mLisernerList выглядит так:

vector<std::weak_ptr<EventHandler>> mListenerList;

EventDispatcher просто вызывает sendEvent() для каждого элемента вектора, чтобы уведомить его о событии.

Позвольте мне привести пример, чтобы продемонстрировать мою проблему. Допустим, мой класс Buttons наследуется от EventHandler. Я создам объекты-кнопки в куче, а затем размещу интеллектуальные указатели на все мои кнопки в одном векторе.

vector<std::shared_ptr<Buttons>> mButtons;   
mButtons.push_back(make_event<Buttons>());
person Mooing Duck    schedule 06.05.2014

Вы не можете наивно использовать std::shared_ptr; есть какая-то специальная поддержка, которая может это позволить, но она слишком сложна и почти наверняка здесь не подходит; нет никакого способа, чтобы EventDispatcher обычно "владел" EventHandler.

Настоящий вопрос здесь заключается в том, почему вы вообще хотите использовать умные указатели здесь? EventHandler регистрируется в своем конструкторе и отменяет регистрацию в своем деструкторе. Указатели в EventDispatcher предназначены исключительно для навигации. То же самое, вероятно, верно и для mButtons, хотя есть конструкции, в которых это может быть не так. (Я склонен немного скептически относиться к векторам unique_ptr. Это зависит от того, где расположен вектор, но из того, что я видел, дополнительная сложность, необходимая для доступа к фактическим указателям, перевешивает сложность обработки удалений вручную.)

person James Kanze    schedule 06.05.2014
comment
Мне нужно было создать несколько объектов в куче, чтобы сохранить их в области видимости. Затем я хотел сохранить их в векторе, чтобы я мог перемещаться по ним (вы были правы). Наконец, у меня есть система обработчиков событий, которую я хотел использовать. Каждый шаг кажется таким простым и логичным. Однако на практике это, кажется, создает беспорядок. Можете ли вы предложить лучший подход? - person user870130; 06.05.2014
comment
Имена (особенно Button) предполагают графический интерфейс. Как правило, компоненты графического интерфейса принадлежат компоненту, который их содержит; дополнительные компоненты, такие как EventHandler (если они не являются базовым классом одного из компонентов GUI), будут принадлежать классу, который они обслуживают. Компоненты, которые где-то регистрируются, будут отменены в своем деструкторе (и реестры будут использовать необработанные указатели). Таким образом, каждый объект будет принадлежать одному другому объекту. Вы можете использовать для этого unique_ptr, но если они находятся в векторе, вам может быть так же легко выполнить необходимое удаление вручную. - person James Kanze; 06.05.2014