【问题标题】:Should referenced std::shared_ptr be deleted after method goes out of scope?方法超出范围后是否应该删除引用的 std::shared_ptr ?
【发布时间】:2015-03-21 17:01:02
【问题描述】:

我正在学习智能指针,学习它比在堆上实现一个简单的结构(如链表)更好。

我创建了一个链表结构如下...

// linked list node definition
#ifndef __LINKED_LIST_NODE_H
#define __LINKED_LIST_NODE_H

class LinkedListNode {
    friend class LinkedList;
public:
    int                 m_value;
    LinkedListNode *    m_pNext;
public:
    LinkedListNode();
    LinkedListNode(int);
    LinkedListNode(const LinkedListNode &);
    ~LinkedListNode();
};

#endif

// linked list definition
#ifndef __LINKED_LIST_H
#define __LINKED_LIST_H

class LinkedList {
    LinkedListNode * m_pHead;
    LinkedListNode * m_pTail;
public:
    LinkedList();
    LinkedList(int);
    LinkedList(const LinkedList &);
    ~LinkedList();
    void PrintList() const;
    void AddItem(int);
    void RemoveItem(int);
    LinkedListNode * FindNode(int) const;
    LinkedListNode * FindMin() const;
    LinkedListNode * FindMax() const;
};

#endif

这里是 LinkedListNode 和 LinkedList 类的必要方法(构造函数和析构函数),看看它的样子(IIRC 这些应该是正确的)...

// list node
LinkedListNode::LinkedListNode()
{
    m_value = 0;
    m_pNext = nullptr;
}

LinkedListNode::LinkedListNode(int value)
{
    m_value = value;
    m_pNext = nullptr;
}

LinkedListNode::LinkedListNode(const LinkedListNode & copyNode)
{
    m_value = copyNode.m_value;
    m_pNext = copyNode.m_pNext;
}

LinkedListNode::~LinkedListNode()
{
    // not needed, no dynamic allocation
}


// linked list
LinkedList::LinkedList()
{
    m_pHead = nullptr;
    m_pTail = m_pHead;
}

LinkedList::LinkedList(int value)
{
    std::shared_ptr<LinkedListNode>newNode{ new LinkedListNode(value) };
    m_pHead = newNode.get();
    m_pHead->m_pNext = nullptr;
    m_pTail = m_pHead;
}

LinkedList::LinkedList(const LinkedList & copyList)
{
    if (copyList.m_pHead == nullptr)
    {
        m_pHead = nullptr;
        m_pTail = m_pHead;
    }
    else
    {
        std::shared_ptr<LinkedListNode>NodeResource{ new LinkedListNode(*copyList.m_pHead) };

        LinkedListNode * tempNode = NodeResource.get();

        m_pHead = tempNode;

        while (tempNode->m_pNext != nullptr)
        {           
            std::shared_ptr<LinkedListNode>NodeResourceNext{ new LinkedListNode(*tempNode->m_pNext) };
            tempNode->m_pNext = NodeResourceNext.get();
            tempNode = NodeResourceNext.get();
        }

        m_pTail = tempNode;
    }
}

LinkedList::~LinkedList()
{
    // not needed, allocating using smart pointers
}

现在,LinkedList 类包含 AddItem 方法,其主体是这样的:

void LinkedList::AddItem(int value)
{
    std::shared_ptr<LinkedListNode>newNode{ new LinkedListNode(value) };

    if (m_pHead == nullptr) // linked list is empty
    {
        m_pHead = newNode.get();
        m_pTail = newNode.get();
    }
    else
    {
        m_pTail->m_pNext = newNode.get();
        m_pTail = newNode.get();
    }
}

我不知道为什么,但是当我尝试将一个项目添加到我的链表时,当你超出该方法的范围时,似乎 newNode 变量被删除了。

这是我尝试调试程序时的样子...

首先我们从空链表开始

然后在 AddItem 函数中,我得到以下结果(看起来 m_pHead 和 m_pTail 正确地指向堆上新创建的 newNode。

但是当 AddItem() 方法超出范围时,这就是我剩下的

我想,一旦没有引用指针,std::share_ptr 就会被删除。在我的例子中,newNode 被两个指针 m_pHead 和 m_pTail 引用。离开 AddItem() 方法时它真的被删除了,还是我的代码中存在我没有发现的缺陷?

非常感谢你们的意见,伙计们。

【问题讨论】:

  • 当引用它的最后一个共享 ptr 被删除时,共享 ptr 将被释放。它不知道您还使用常规指针指向它。
  • 您可能会认为这些屏幕截图有帮助,但实际上却很伤人:它清楚地表明您在问题上付出了一些努力,但这让我们很难回答,因为我们无法复制任何内容并且必须重新输入所有内容。
  • @ereOn 很抱歉给您带来不便,我对这个网站不是很有经验。
  • @Andy:不用担心。 SO的好处是您可以随时更新/修复您的问题/答案以造福所有人:)

标签: c++ c++11 shared-ptr smart-pointers singly-linked-list


【解决方案1】:

您的问题太长了,但我看到您似乎没有以适当的方式使用std::shared_ptr。例如,

LinkedList::LinkedList(int value)
{
    std::shared_ptr<LinkedListNode>newNode{ new LinkedListNode(value) };
    m_pHead = newNode.get();
    m_pHead->m_pNext = nullptr;
    m_pTail = m_pHead;
}   // <-- call of std::shared_ptr::~std::shared_ptr

newNode 将在函数体的末尾被销毁。然后,这还将删除共享指针的对象(恰好在一个std::shared_ptr 之间),从而使m_pHead 留下一个悬空指针。

shared_ptr 的基本思想是共享某些资源的所有权,只要任何共享所有者还活着,那么资源也是如此:资源是当最后一个所有者死亡时删除。

在绝大多数好的代码中,资源归某些对象所有,即共享所有权是一种相当罕见和小众的情况。在简单的链表实现中绝对不需要它。


链表的一种可能的所有权模型是每个节点都拥有下一个节点。那么你会有

template<typename T> class list;
template<typename T>
class node {
  friend class list<T>;
  T m_data;
  unique_ptr<node> m_next;
  explicit node(T const&value)
  : m_data(value) {}
  node(T const&value, unique_ptr<node>&&n)
  : m_data(value), m_next(n) {}
public:
  node*next() { return m_next.get(); }
  const node*next() const { return m_next.get(); }
};

template<typename T>
class list {
  typedef node<T> node_type;
  unique_ptr<node_type> m_head;
  node_type            *m_tail;
  ~list() {}  // destructor of m_head destroys all nodes recursively
  explicit list(T const&value)
  : m_head(new node_type(value)), m_tail(m_head.get()) {}
  void push(T const&value)
  {
    // move the existing m_head to be m_next of the new node,
    // which in turn becomes the new m_head. m_tail is unaffected.
    m_head = new node_type(value,std::move(m_head));
  }
};

但是,您必须小心如何实现插入和切片等。

【讨论】:

  • std::shared_ptr 除了你认为在这种情况下使用std::unique_ptr 可能是一个更好(或唯一可行)的想法吗?
  • @Andy 在决定要使用哪种智能指针之前,您首先需要有一个一致的所有权模型。你认为谁应该拥有LinkedListNode?
  • 是的,使用unique_ptr 表示LinkedListNode::m_pNext 和LinkedList::m_pHead,但不要使用m_pTail(因为这只是一个观察者)。这将确保由LinkedList 的析构函数自动删除所有节点(实际上您不需要实现该析构函数)。
  • @RedAlert 拥有的节点肯定是LinkedList 所在的LinkedListNode。因此std::unique_ptr 是不是更好?
  • @Walter 我尝试将 LinkedListNode 类中的LinkedListNode * m_pNext 更改为智能指针,然后我无法访问 m_pNext 变量。你知道这是为什么吗?我还没有找到任何关于这个主题的详细文档。
【解决方案2】:

您正在将 newNode.get() 分配给您的 LinkedListNode 的原始指针 (LinkedListNode*) 成员。

这是不正确的,因为shared_ptr(至少在这种情况下)拥有底层指针。当它超出范围时,相关的内存会被释放,但您的 LinkedListNode 仍然有以前分配的内存的成员。

您可能应该更改LinkedListNode 的定义,使其LinkedListNode* 成员改为shared_ptr&lt;LinkedListNode&gt;,确保只要您的实例存在,就会引用底层内存。

【讨论】:

  • 哦,我明白了,所以使用 .get() 方法和 std::shared_ptr 实际上并没有添加对变量的引用?这意味着编译器不知道某些东西实际上指向变量,因此当它超出范围时将其删除?我理解正确吗?
  • 不需要shared_ptr,他只需要m_pHead和m_pNextunique_ptrs就可以了
  • @RedAlert: 可能,但我们没有足够的上下文来判断是否应该使用shared_ptr 或unique_ptr(据我们所知,其他一些代码可能需要shared_ptr节点)。话虽如此,要使用的智能指针的选择可能不是这个问题要解决的第一个问题。
  • 顺便说一句,列表实现中的智能指针很可怕——如果列表很大,你会得到非常深的调用堆栈,因为清理是在析构函数中递归完成的......
  • @ereOn 有人告诉我指针不能拥有资源。例如。对象 * 新对象 = 新对象();是一种不好的做法。有人告诉我,使用智能指针来管理堆上的内存是一种更好的方法。有错吗?
猜你喜欢
  • 2014-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-06
  • 1970-01-01
相关资源
最近更新 更多