2013-05-04 4 views
2

Предположим, что у меня есть класс, как это:Указав (не) передача права собственности с unique_ptr

class Node { 
public: 
    Node(Node* parent = 0) : mParent(parent) {} 
    virtual ~Node() { 
     for(auto p : mChildren) delete p; 
    } 

    // Takes ownership 
    void addChild(Node* n); 

    // Returns object with ownership 
    Node* firstChild() const; 

    // Does not take ownership 
    void setParent(Node* n) { mParent = n; } 

    // Returns parent, does not transfer ownership 
    Node* parent() const { return mParent; } 

private: 
list<Node*> mChildren; 
Node* mParent; 
}; 

Я бы сейчас хотел бы использовать смарт-указатели и/или ссылки на RValue, чтобы указать, где собственность и не является переданы.

Мое первое предположение заключалось бы в изменении mChildren, чтобы содержать unique_ptr s, адаптируя сигнатуры функций следующим образом.

// Takes ownership 
    void addChild(unique_ptr<Node> n); 

    // Returns object with ownership 
    unique_ptr<Node>& firstChild() const; 

    // Does not take ownership 
    void setParent(Node* n) { mParent = n; } 

    // Returns parent, does not transfer ownership 
    Node* parent() const { return mParent; } 

Теперь, это будет своего рода проблематичным, когда мне нужно передать результат в Node::firstChild() к некоторой функции, которая наблюдает, но не брать на себя ответственность, так как я должен был бы явно вызвать .get() на unique_ptr, который, как я понимаю, не рекомендуется.

Каков правильный и рекомендуемый способ указать право собственности с использованием unique_ptr, не прибегая к использованию .get() и проходящего вокруг голых указателей?

+0

Почему бы не передать его функции как ссылку на const unique_ptr? Таким образом, функция может заглянуть в нее, и никакая копия не будет сделана. – JBL

+0

@ JBL, поскольку, например, 'parent()' возвращает не уникальный указатель, то есть мне придется перегружать каждую функцию для сырых и уникальных указателей. – Symaxion

ответ

7

Сначала я использовал бы std::vector, а не std::list, чтобы содержать детей. Если у вас нет сильной мотивации для , а не, то он должен быть контейнером по умолчанию, std::vector. Если вас беспокоит производительность, не будьте, потому что смежное распределение, сделанное std::vector, скорее всего, вызовет более высокую скорость попадания в кэш, что значительно ускорит доступ к std::list, что подразумевает разброс схемы распределения/доступа.

Во-вторых, вы правы в том, что у вас есть std::vector<std::unique_ptr<Node>> для удержания детей, так как разумно предположить, что узел имеет право владеть дочерними узлами. С другой стороны, все остальные указатели, кроме принятого addChild(), должны быть не владеющими исходными указателями.

Это относится к указателю mParent и указателям, возвращаемым функциями члена Node. Фактически функция члена firstChild() может даже вернуть ссылку , выдавая исключение, если узел не имеет дочерних элементов. Таким образом, вы не создадите путаницы в том, кто владеет возвращенным объектом.

Возвращение unique_ptr, или ссылку на unique_ptr, не является правильным идиома: уникальные указатели представляют собой право собственности, и вы не хотите, чтобы дать право собственности клиентам Node.

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

#include <vector> 
#include <memory> 
#include <stdexcept> 

class Node { 
public: 
    Node() : mParent(nullptr) { } 

    void addChild(std::unique_ptr<Node>&& ptr) { 
     mChildren.push_back(std::move(ptr)); 
     ptr->setParent(this); 
    } 

    Node& firstChild() const { 
     if (mChildren.size() == 0) { throw std::logic_error("No children"); } 
     else return *(mChildren[0].get()); 
    } 

    Node& parent() const { 
     if (mParent == nullptr) { throw std::logic_error("No parent"); } 
     else return *mParent; 
    } 

private: 

    void setParent(Node* n) { 
     mParent = n; 
    } 

    std::vector<std::unique_ptr<Node>> mChildren; 
    Node* mParent; 
}; 

Вы, конечно, можете принять решение о возвращении не-ВЛАДЕЮЩЕМ, потенциально нулевые сырые указатели вместо ссылки, если вы хотите избежать бросать исключения. Или вы можете добавить пару методов hasParent() и getNumOfChildren() для получения информации о состоянии Node. Это позволит клиентам выполнить проверку, если они не хотят обрабатывать исключения.

+0

Мне, возможно, придется сделать много средних вставок/абстракций, поэтому я изначально выбрал std :: list over std :: vector. – Symaxion

+0

@SeySayux: 'std :: vector' может по-прежнему быть превосходным из-за более высокой скорости попадания в кеш (хотя сложность еще хуже, на практике доминирует шаблон доступа к памяти). Попробуйте выполнить некоторые измерения до того, как вы зафиксируете 'std :: list'. –

+0

Нужно ли/целесообразно использовать 'unique_ptr' значение rvalue-ref вместо значения? – dyp

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

  • Нет связанных вопросов^_^