2015-01-20 3 views
2

При использовании enabled_shared_from_this, по-видимому, есть некоторые края. Например:Может ли shared_from_this быть реализован без enable_shared_from_this?

boost shared_from_this and multiple inheritance

Может shared_from_this быть реализованы без необходимости enable_shared_from_this? Если да, можно ли это сделать так быстро?

+0

Вы имеете в виду это как замену/эквивалент C++ 11, для другого вопроса, связанного с форсированием? –

+0

C++ 11. Есть ли что-то другое в версии C++ 11, о которой я должен знать? – Taylor

+0

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

ответ

0

Да, он может использовать глобальные хеш-таблицы типа

unordered_map< T*, weak_ptr<T> > 

для выполнения поиска совместно используемого указателя от this.

#include <memory> 
#include <iostream> 
#include <unordered_map> 
#include <cassert> 

using namespace std; 

template<class T> 
struct MySharedFromThis { 
    static unordered_map<T*, weak_ptr<T> > map; 

    static std::shared_ptr<T> Find(T* p) { 
     auto iter = map.find(p); 

     if(iter == map.end()) 
      return nullptr; 

     auto shared = iter->second.lock(); 
     if(shared == nullptr) 
      throw bad_weak_ptr(); 

     return shared; 
    } 
}; 

template<class T> 
unordered_map<T*, weak_ptr<T> > MySharedFromThis<T>::map; 

template<class T> 
struct MyDeleter { 
    void operator()(T * p) { 
     std::cout << "deleter called" << std::endl; 
     auto& map = MySharedFromThis<T>::map; 

     auto iter = map.find(p); 
     assert(iter != map.end()); 
     map.erase(iter); 
     delete p; 
    } 
}; 

template<class T> 
shared_ptr<T> MyMakeShared() { 
    auto p = shared_ptr<T>(new T, MyDeleter<T>()); 
    MySharedFromThis<T>::map[p.get()] = p; 
    return p; 
} 

struct Test { 
    shared_ptr<Test> GetShared() { return MySharedFromThis<Test>::Find(this); } 
}; 

int main() { 
    auto p = MyMakeShared<Test>(); 

    assert(p); 
    assert(p->GetShared() == p); 
} 

Live Demo

Однако, карта должна быть обновлена ​​всякий раз, когда shared_ptr конструируется из T *, и до того, как Deleter называется, время калькуляции. Кроме того, чтобы быть потокобезопасным, мьютекс должен был защищать доступ к карте, сериализовывая распределения одного и того же типа между потоками. Таким образом, эта реализация не будет работать так же, как и enable_shared_from_this.

Update:

Улучшение на это, используя те же приемы, указатели, используемые make_shared, здесь реализация которых должна быть столь же быстро, как shared_from_this.

Live Demo

+1

Потому что ... что? Вы сравнили одну вещь с ... 'void()'. _ «так что эта реализация не будет работать так хорошо». Я рад, что у вас есть понимание, но если вы хотите с пользой поделиться этим пониманием (и получить оценку за это, без сомнения), это хорошая идея для его фактического изложения. (В противном случае это похоже на раздачу ваших незагруженных заметок в салфетке редактору книг и произнесение «вы можете опубликовать это» _) – sehe

+0

Правильно, потому что переполнение стека каким-то образом эквивалентно формальности публикации книги. ЛОЛ! Более длинный ответ выше повторяет то, что я сказал («удар по производительности по сравнению с текущим состоянием для людей, которые в этом нуждаются (мьютексы, поиск и т. Д.)»). Изложение слова - это существительное, а не глагол. Меня интересует информация, а не точки. – Taylor

+1

Re: «формальность», это не то, что я имел в виду.Я указал, что ваши рассуждения здесь неполны. По сути, это и не полезно (люди не могут следовать вашим рассуждениям, чтобы получить ваше понимание) и не поддаются проверке. (Спасибо за исправление моего английского) – sehe

6

А shared_ptr 3 вещи. Это контрольный счетчик, разрушитель и принадлежащий ему ресурс.

Когда вы make_shared, он выделяет все 3 сразу, а затем создает их в одном блоке.

Когда вы создаете shared_ptr<T> с T*, вы создаете контрольный счетчик/эсминца отдельно и обратите внимание, что принадлежащий ему ресурс - это T*.

Цель shared_from_this состоит в том, что мы можем извлечь shared_ptr<T> из T* в основном (в предположении, что он существует).

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

Однако не все общие указатели создаются именно так. Иногда вы можете создать общий указатель на объект, который не был создан никакой функцией библиотеки std, и, следовательно, T* не связан с данными подсчета и уничтожения ссылок общего указателя.

Как нет номер в T* или что он указывает (в целом), чтобы найти такие конструкции, мы должны хранить его снаружи, что означает глобальное состояние и потокобезопасности накладные расходы и другие боли.Это будет обузой для людей, которые не нуждаются в shared_from_this, и производительность по сравнению с текущим состоянием для людей, которые в ней нуждаются (мьютексы, поиск и т. Д.).

Настоящий проект хранит weak_ptr<T> в enable_shared_from_this<T>. Этот weak_ptr инициализируется всякий раз, когда вызывается make_shared или shared_ptr<T> ctor. Теперь мы можем создать shared_ptr<T> из T*, потому что у нас есть «сделанная комната» для него в классе, наследуя от enable_shared_from_this<T>.

Это очень низкая стоимость и очень хорошо обрабатывает простые случаи. Мы закончили с накладными расходами одного weak_ptr<T> по базовой стоимости T.

Если у вас есть два различных shared_from_this, их weak_ptr<A> и weak_ptr<B> члены не имеют никакого отношения, поэтому он является неоднозначным, где вы хотите сохранить получившийся смарт-указатель (возможно, и другое?). Эта двусмысленность приводит к ошибке, которую вы видите, так как предполагает, что в одном уникальном shared_from_this<?> есть один член weak_ptr<?>, и на самом деле есть два.

The linked solution обеспечивает умный способ продлить это. Он пишет enable_shared_from_this_virtual<T>.

Здесь вместо хранения weak_ptr<T>, мы храним weak_ptr<Q> где Q является виртуальным базовым классом enable_shared_from_this_virtual<T>, и делает это уникально в виртуальном базовом классе. Затем он практически не переопределяет shared_from_this и аналогичные методы обеспечивают тот же интерфейс, что и shared_from_this<T>, используя «указатель-член или дочерний тип конструктора shared_ptr», в котором вы разбиваете компонент счетчика/уничтожителя ссылок из принадлежащего ему ресурсного компонента в безопасном типе путь.

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

Преимущество «просто работает». В иерархии теперь есть один уникальный shared_from_this<?>, и вы все равно можете получить общие указатели типа к классам T, которые наследуются от shared_from_this<T>.

+0

Было бы здорово иметь версию shared_from_this, предполагающую, что объект был выделен с использованием make_shared (и избегает наследования). Я бы использовал его! – Taylor

+0

@ Тейлор вам также нужно знать, какой тип он был сконструирован как, теперь, когда я думаю об этом, - если у нас есть указатель на Q, а Q находится со смещением 47 от начала объекта, который на самом деле W, найти общий блок указателя из 'Q *' является сложной задачей. Это может быть выполнимо с таблицей виртуального наследования, но я не уверен. – Yakk

+0

Я никогда не пользуюсь виртуальным наследованием, поэтому мне бы хотелось что-то (явно не в std), которое просто делало простой случай простым. – Taylor

Смежные вопросы