如何将unique_ptr用作树中的子项

时间:2018-03-09 11:08:25

标签: c++ tree

我正在尝试使用unique_ptr作为子项创建一个树。这是目前的课程:

class Part
{
public:
    vector<Part>& getChildren() const {
        return *m_children;
    }

    void attachChild(const unique_ptr<Part>& child) {
        m_children.push_back(std::move(child));
    }

    vector<Part>& getAtoms() const {
        vector<Part> atoms;

        for (const auto& child : m_children) {
            if (child->hasChildren()) {
                vector<Part> childChildren = child->getAtoms();
                atoms.insert(atoms.end(), childChildren.begin(), childChildren.end());
            } else {
                atoms.push_back(child);
            }
        }

        return atoms;
    }

    vector<Part>& getAbsoluteAtoms() const {
        vector<Part> atoms;

        for (auto child : m_children) { // Not const because I modify the child
            if (child->hasChildren()) {
                vector<Part> childChildren = child->getAbsoluteAtoms();
                atoms.insert(atoms.end(), childChildren.begin(), childChildren.end());
            } else {
                child.setPosition(child->getPosition() + m_position);
                atoms.push_back(child);
            }
        }

        return atoms;
    }

private:
    vector<unique_ptr<Part>> m_children;
};

我有很多错误,因为指针就像这一个:

D:\Programmes\Qt\Tools\mingw530_32\i686-w64-mingw32\include\c++\bits\stl_construct.h:75: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Part; _Dp = std::default_delete<Part>]'
     { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
       ^

没有它们,一切都运作良好,但是当孩子变得非常庞大时,我需要它们。你能告诉我为什么我的代码不正确吗?

2 个答案:

答案 0 :(得分:2)

在充分尊重的情况下,我认为你需要研究C ++的基础知识。对我来说,使用此代码推断出您想要实现的目标是不可能的。也许我可以帮助你指出一些错误,因为我认为这应该是你的第一步。

使用std :: move

void attachChild(const unique_ptr<Part>& child) {
    m_children.push_back(std::move(child));
}

为什么要移动const&amp;?如果它不是一个恒定的参考,你认为值得移动吗?为什么这是unique_ptr? m_children包含unique_ptr类型的元素,并且您想要使用addChild方法填充该向量?我们可以复制unique_ptr吗?他们不再是独一无二的了。所有这一切都很奇怪。无法理解你的意图。

返回类型

vector<Part>& getAtoms() const { vector<Part> atoms; /* fill atoms */ return atoms; }

您确定要返回对getAtoms()函数末尾被破坏的变量的引用吗?返回类型应该是

vector<Part>

同样在这里:

vector<Part>& getAbsoluteAtoms() const { ... }
需要

vector<unique_ptr<Part>> m_children

vector<unique_ptr<Part>> m_children;    

我真的很想知道为什么你需要在类属性中存储unique_ptr的向量。我确定你有理由。但在继续之前,我将重新审视C ++的基础知识(复制,引用,指针,移动语义......)。

答案 1 :(得分:0)

您提到的错误告诉您使用unique_ptr的已删除副本构造函数。它被删除的原因是,顾名思义,必须始终只有一个拥有已分配对象的unique_ptr

此复制构造函数的一个用途是在Part类的默认生成的复制构造函数中,例如,只要您将Part插入vector<Part>,就会使用该构造函数。另一个用途是for (auto child : m_children)循环。

std :: unique_ptr只能移动,这意味着在移动到新的unique_ptr后,源不会引用已分配的对象。由于unique_ptr的目的是确保在所有情况下都能正确删除拥有的对象,并且永远不会删除两次,因此无法盲目复制它。

通过使用std :: unique_ptr,您表示父“拥有”子项,即通过复制父项,您通过删除父项删除子项来复制子项。

这适用于树,因为每个节点最多只有一个父节点,你可以处理根。 如果Part表示有向非循环图,则可以使用shared_ptr

这就是我如何编写具有分层所有权的Part

#include <vector>
#include <memory>

class Part
{
public:
    // or whatever constructor is useful
    Part() = default;

    ~Part() = default;

    // copy constructor needed because we cannot copy unique_ptr
    Part(const Part &other) {
        // cannot use just auto here, as that would try to copy the unique_ptrs
        for(const auto &child_ptr : other.m_children) {
            // we recursively copy all children
            m_children.emplace_back(std::make_unique<Part>(*child_ptr));
        }
    }

    // move constructor does what we expect
    Part(Part && other) = default;

    // since we need an explicit copy constructor it is
    // good practice to mention also the assignment operators
    // see also the "rule of five"
    // copy assignment should be similar to the copy constructor, but first clear the children
    Part &operator=(const Part &other) {
        m_children.clear();
        for(const auto &child_ptr : other.m_children) {
            // we recursively copy all children
            m_children.emplace_back(std::make_unique<Part>(*child_ptr));
        }
        return *this;
    }

    // moving should work as expected, we get all the children of other
    Part &operator=(Part &&other) = default;


    const std::vector<std::unique_ptr<Part>>& getChildren() const {
        return m_children;
    }

    // we take a unique_ptr by value because it (the pointer) is small
    void attachChild(std::unique_ptr<Part> child) {
        m_children.push_back(std::move(child));
    }

    bool hasChildren() const {
        return !m_children.empty();
    }

private:
    std::vector<std::unique_ptr<Part>> m_children;
};

// note how we return the vector by value, 
// to avoid passing a stale reference
// the user will get a completely new vector
std::vector<Part> makeLotsOfParts(const Part &part) {
    std::vector<Part> parts;
    for(int i = 0; i < 10; ++i) {
        // now we can copy parts!
        parts.push_back(part);
    }
    // here the compiler will either apply the return value optimization
    // or move the vector cheaply into the return value
    return parts;
}

std::unique_ptr<Part> assemblePart() {
    std::unique_ptr<Part> parent = std::make_unique<Part>();
    std::unique_ptr<Part> child1 = std::make_unique<Part>();
    // we do not need child1 any more, so we move from it
    parent->attachChild(std::move(child1));

    std::unique_ptr<Part> child2 = std::make_unique<Part>();
    parent->attachChild(std::move(child2));

    // again we can rely on RVO or move
    return parent;    
}