2016-09-08 7 views
2

Я в ситуации, когда я не уверен, какой тип умного указателя использовать, потому что я не уверен во всех случаях использования для моего класса. Я мог бы просто использовать общие указатели, но я не люблю идею передачи общих указателей везде в моем коде, когда мне не обязательно нужна совместная собственность. This article Herb Sutter говорит, что когда вы сомневаетесь, используйте unique_ptr и конвертируйте в shared_ptr, когда вам нужно. Это то, что я хотел бы сделать, но я неясно, как это должно быть сделано, рассмотрим следующий пример:shared_ptr и unique_ptr conversion

class Example 
{ 
    public: 
     Example(): _ptr(std::make_unique<Node>()) {} 

     std::unique_ptr<Node>& getPtr() 
     { 
      return _ptr; 
     } 

    private: 
     // I am unsure if I will eventually need shared ownership or not 
     std::unique_ptr<Node> _ptr; 
}; 

Example* example = new Example(); 


// Some function somewhere 
void f() 
{ 
    // I've decided I need shared ownership, converting 
    std::shared_ptr<Node> ptr(std::move(example->getPtr())); 

    // Oops, example is no longer valid... 
} 

Если кто-то имеет лучшее представление о том, как иметь дело с ситуациями, как этот I «Рад это услышать.

+2

unique_ptr и shared_ptr предназначены для ВЛАДЕЛЬЦА используемой памяти, а не для ее простого использования. Вы часто не пропускаете shared_ptr, поскольку они относительно дороги, чтобы сделать, по сравнению с обычными указателями/ссылками. – xaxxon

+0

Будет ли 'Example' * всегда * необходимо поддерживать право собственности на свой узел, а иногда другие объекты также требуют владения этим узлом? – jaggedSpire

+1

Из приведенного выше примера кажется, что ваш указатель должен быть 'shared_ptr', как вы хотите (потенциально) поделиться собственностью. Сомнение может случиться для фабрики. – Jarod42

ответ

1

Я думаю, что вы задаете какой-то вопрос оптимизации.Вы хотите, чтобы Example использовал unique_ptr, потому что он имеет более простую и эффективную семантику (перефразируя вашу ссылку). Но, когда возникнет такая необходимость, вы хотите разрешить преобразование указателя в shared_ptr.

Example должен просто предоставить интерфейс для этого и сам должен преобразовать из unique_ptr в shared_ptr, когда его пользователь вызывает этот интерфейс. Вы можете использовать шаблон состояния для определения того, находится ли экземпляр в режиме unique_ptr или shared_ptr.

class Example 
{ 
    struct StateUnique; 
    struct StateShared; 
    struct State { 
     State (std::unique_ptr<State> &s) : _state(s) {} 
     virtual ~State() = default; 
     virtual Node & getPtr() = 0; 
     virtual std::shared_ptr<Node> & getShared() = 0; 
     std::unique_ptr<State> &_state; 
    }; 
    struct StateUnique : State { 
     StateUnique (std::unique_ptr<State> &s) 
      : State(s), _ptr(std::make_unique<Node>()) {} 
     Node & getPtr() { return *_ptr.get(); } 
     std::shared_ptr<Node> & getShared() { 
      _state = std::make_unique<StateShared>(*this); 
      return _state->getShared(); 
     } 
     std::unique_ptr<Node> _ptr; 
    }; 
    struct StateShared : State { 
     StateShared (StateUnique &u) 
      : State(u._state), _ptr(std::move(u._ptr)) {} 
     Node & getPtr() { return *_ptr.get(); } 
     std::shared_ptr<Node> & getShared() { return _ptr; } 
     std::shared_ptr<Node> _ptr; 
    }; 
public: 
    Example(): _state(std::make_unique<StateUnique>(_state)) {} 
    Node & getNode() { return _state->getPtr(); } 
    std::shared_ptr<Node> & getShared() { return _state->getShared(); } 
private: 
    std::unique_ptr<State> _state; 
}; 

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

class Example 
{ 
public: 
    Example(): _u_node(std::make_unique<Node>()) {} 
    Node & getNode() { return _u_node ? *_u_node.get() : *_s_node.get(); } 
    std::shared_ptr<Node> & getShared() { 
     if (_u_node) _s_node = std::move(_u_node); 
     return _s_node; 
    } 
private: 
    std::unique_ptr<Node> _u_node; 
    std::shared_ptr<Node> _s_node; 
}; 
+0

Благодарим вас, что это по существу то, что я хотел сделать, однако я очень хотел использовать этот метод в своем коде (не только в этом классе), и этот шаблон для этого слишком сложна. Однако это правильный ответ для тех, кто хочет это сделать. – navark

+0

@navark: Я добавил упрощенную версию в нижней части моего ответа. – jxh

+0

Почему у вас есть как unique_ptr, так и shared_ptr в классе? Единственная добавочная стоимость shared_ptr над нормальным указателем заключается в создании, поэтому нет причин не просто создавать его спереди - не усложняйте свой код путем преобразования взад и вперед. Если это когда-нибудь будет shared_ptr, просто сделайте shared_ptr. И с точки зрения понимания собственности вы никогда не сможете рассматривать это как unique_ptr, вам всегда нужно предположить, что он использует версию shared_ptr с точки зрения статического анализа. Вы действительно просто добавляете сложные случаи отказа практически без выгоды – xaxxon

1

Если у вас есть класс, скажем UniqueResourceHolder, он может быть реализован следующим образом:

class UniqueResourceHolder{ 
public: 
    std::unique_ptr<Widget> ptr; 
}; 

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

class SharedResourceHolder{ 
public: 
    std::shared_ptr<Widget> ptr; 
}; 

код, чтобы сделать это может выглядеть следующим образом:

{ 
    UniqueResourceHolder urh; 
    SharedResourceHolder srh; 
    //initialization of urh.ptr 

    srh.ptr = std::shared_ptr<Widget>(urh.release()); 
} 

UniqueResourceHolder больше не будет иметь Widget, так что любая попытка использовать urh для доступа к виджету будет недействительной (если вы не засете ее новым), но у srh теперь будет этот виджет и он захочет доля. Во всяком случае, так я понимаю ответ на вопрос в указанной вами ссылке. Мои собственные два цента - это также тривиальный вопрос, чтобы заменить вхождения std::unique_ptr на std::shared_ptr; любая бизнес-логика, которую ваша программа выполняла для обеспечения уникальности ресурса, по-прежнему действительна, но чтобы перейти от бизнес-логики, где вы воспользовались общим характером std::shared_ptr, потребуется некоторое время и сосредоточиться на переделке с уникальным мышлением.

+0

Вы можете просто сделать 'srh.ptr = std :: move (urh.ptr);'. – Jarod42

+0

Это не совсем то, что я хотел сделать, но я думаю, что вы правы в том, что замена unique_ptr на shared_ptr, когда это необходимо, является тривиальным и может быть лучшим решением. – navark

0

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

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

Относительно производительности: сказано, что по сравнению с примитивным указателем, общий указатель примерно в 6 раз медленнее. Уникальный указатель примерно в два раза медленнее. Конечно, я говорю о доступе. Если у вас действительно есть критически важные для производительности части кода, вы все равно можете получить примитивные указатели этих классов и работать. Это даже не должно быть указателем, все еще есть ссылки на C++.

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

std::unique_ptr<Node>& getPtr() 
{ 
    return _ptr; 
} 

Нет, нет. Вы передаете несколько ссылок, если вы называете это из разных мест. Вместо этого используйте оператор «move» (например, с объявлениями, например type& operator=(type&& other)) и передайте свой уникальный указатель.

+0

Вы уверены, что именно так должны использоваться уникальные указатели, потому что мое понимание было что «уникальность» принадлежала ему, но, ссылаясь на нее, вы могли передавать необработанные указатели (или ссылаться на unique_ptr) на этот unique_ptr столько, сколько хотите – navark

+0

Не уверен, разве мы не говорим об этом? «Перемещение» не называется «изменением», но все-таки это указатель в конце концов. Я предполагаю, что это как объект/указатель, перемещаемый по программе, может быть, моя собственная фантазия. Если вы передаете необработанные указатели, вы «делитесь», не пользуясь преимуществом этих классов. Я думаю, что получение исходных указателей - это больше, потому что у вас также есть другие способы получить этот указатель. В конце концов, вы используете классы шаблонов. –

+0

unique_ptr не медленнее, чем исходные указатели, если у вас есть хотя бы небольшая оптимизация. – aschepler