【问题标题】:Implementing Containers using Smart Pointers使用智能指针实现容器
【发布时间】:2011-07-25 04:15:44
【问题描述】:

好的,所以每个人都知道应该像瘟疫一样避免原始指针并更喜欢智能指针,但是这个建议在实现容器时是否适用?这就是我想要完成的:

template<typename T> class AVLTreeNode {
public:
    T data;
    unique_ptr<AVLTreeNode<T>> left, right;
    int height;
}

Unique_ptr 可以使容器函数编写起来更麻烦,因为我不能让多个原始指针以一种优雅的方式临时指向同一个对象。例如:

unique_ptr<AVLTreeNode<T>> rotate_right(unique_ptr<AVLTreeNode<T>> n1)
{
    unique_ptr<AVLTreeNode<T>> n2 = n1->left;

    n1->left = n2->right;
    n2->right = n1;
    // n1 must now be referenced through the longer name n2->right from now on
    n2->right->recalculate_height();
    n2->recalculate_height();

    return n2;
}

(在这个例子中这没什么大不了的,但我可以想象它怎么会成为一个问题)。我是否应该将这些问题作为强烈暗示容器应该使用旧的newdelete 和原始指针来实现?避免编写析构函数似乎非常麻烦。

【问题讨论】:

  • 除了一次用传统的包含防护代替 pragma 之外,我已经复制了您的代码并且不使用 clang/libc++ 复制您的错误。我建议预处理 driver.cpp,检查它,看看你是否在其中看到 unique_ptr。您看到的行为可能与unique_ptr 无关,例如不包括&lt;memory&gt; 的C++11 版本。

标签: c++ c++11 containers smart-pointers unique-ptr


【解决方案1】:

Herb Shutter 在his GoTW series 中对不使用 shared_ptr 作为参数有非常明确的指导:

准则:不要将智能指针作为函数参数传递,除非 您想使用或操作智能指针本身,例如 共享或转让所有权。

还有这个……

准则:更喜欢按值、* 或 & 传递对象,而不是通过智能传递对象 指针。

【讨论】:

    【解决方案2】:

    如您所示,在实现容器时,我通常不使用智能指针。原始指针(恕我直言)像瘟疫一样要避免。当您想要强制执行内存所有权时,请使用智能指针。但通常在容器中,容器拥有构成数据结构的指针所指向的内存。

    如果在您的设计中,AVLTreeNode 唯一地拥有它的左右子节点,并且您想用 unique_ptr 来表达这一点,那很好。但是,如果您希望 AVLTree 拥有所有 AVLTreeNodes,并且使用原始指针这样做,那同样有效(并且是我通常编码的方式)。

    相信我,我不反对智能指针。我是发明unique_ptr的人。但unique_ptr 只是工具箱中的另一个工具。在工具箱中拥有好的智能指针并不是万能的,盲目地使用它们并不能代替精心设计。

    更新回复评论(评论框太小):

    我经常使用原始指针(很少拥有)。开源项目libc++ 中有一个很好的我的编码风格示例。可以在“Browse SVN”链接下浏览源代码。

    出于异常安全的考虑,我更喜欢资源的每次分配都可以在某个析构函数中释放,即使通常的释放发生在析构函数之外。当分配由单个指针拥有时,智能指针通常是工具箱中最方便的工具。当分配由比指针更大的东西(例如容器或类Employee)拥有时,原始指针通常是构成更大对象的数据结构的一个方便部分。

    最重要的是,我从不分配任何资源而不知道哪个对象拥有该资源,无论是智能指针、容器还是其他。

    【讨论】:

    • 有趣。在其他什么场合(除了与 C 代码交互)你更喜欢原始指针而不是智能指针?
    • @HowardHinnant 嗨,我迟到了 7 年多。对于容器不使用智能指针,您是否仍然持有相同的观点?我正在自学数据结构,碰巧我目前拥有的每一本教科书(即使是在引入智能指针之后肯定出现的新教科书)都使用原始指针。我认为这是有道理的。
    • @NicholasHumphrey 迟到总比没有好。 ;-) 我对这个问题的看法仍然大致相同。我不反对内部使用智能指针的容器设计,只要它是正确的。但通常对于我自己的代码(而且我不再编写那么多容器),我会让容器来代替智能指针。请参阅P0083 了解一个很好的示例,其中所有权可以在“智能指针”(node_handle) 和容器之间来回转移以实现相当改进的功能。
    • @HowardHinnant 谢谢霍华德,感谢您的解释!
    【解决方案3】:

    std::shared_ptr 没有这些限制。尤其是多个shared_ptr-instances 可以引用同一个对象。

    【讨论】:

      【解决方案4】:

      小修正:不应该像瘟疫一样避免使用原始指针(哎呀,不是每个人都知道这个事实),但应该尽可能避免手动内存管理(通过使用容器而不是动态数组或智能指针),所以在你的函数中,只需在您的 unique_ptr 上执行 get() 以进行临时存储。

      【讨论】:

        【解决方案5】:

        您提供的代码编译没有问题

        #include <memory>
        template<typename T> class AVLTreeNode {
        public:
            T data;
            std::unique_ptr<AVLTreeNode<T>> left, right;
            int height;
        };
        int main()
        {
            AVLTreeNode<int> node;
        }
        

        测试编译:https://ideone.com/aUAHs

        就我个人而言,我一直在对树使用智能指针,即使我们唯一的东西是 std::auto_ptr

        至于rotate_right,它可以通过调用unique_ptr::swap来实现

        【讨论】:

        • 那……很奇怪。 VC++ 编译了您刚刚提供的内容,但拒绝了我的实现。我已经编辑了问题以包含我的代码,不胜感激为什么它不能编译的帮助。
        • @da_code_monkey VC++ 出了点问题。您的完整示例使用 gcc 4.5.2 编译,但如果我在 main 的开头添加 AVLTreeNode&lt;int&gt; dummy;,在 AVLTree a; 之前,它只能在 VC++ 2010 EE 上编译
        猜你喜欢
        • 2014-03-04
        • 2011-06-14
        • 1970-01-01
        • 2011-08-14
        • 2022-01-23
        • 1970-01-01
        • 2020-08-26
        • 2019-07-09
        • 1970-01-01
        相关资源
        最近更新 更多