【问题标题】:Do I need to set my destructor methods when I am using shared pointers?使用共享指针时是否需要设置析构函数?
【发布时间】:2014-12-02 00:43:57
【问题描述】:

我尝试寻找答案,但没有找到针对我的特定问题的答案。我正在将共享指针用于三元搜索树(用于预测文本算法),并且在使用共享指针时遇到了一些问题。

我已经离开 C++ 5 年了,让我告诉你,Java 并不能帮助你学习指针。在过去的几天里,我不得不重新学习 5-6 年前在学校学到的指针材料,并成功地破坏了我的代码。

这是我拥有的大部分代码:

// TernarySearchTree.cc

#include "stdafx.h"
#include "ternary_search_tree.h"

//Constructor
TernarySearchTree::TernarySearchTree() {
  num_nodes_ = 0;
  size_in_memory_ = 0;
  root_node_ = nullptr;
}

TernarySearchTree::TernarySearchTree(const TernarySearchTree& other) {
  num_nodes_ = other.num_nodes_;
  size_in_memory_ = other.size_in_memory_;

  TernarySearchTreeNode node;
  node = *other.root_node_;
  root_node_.reset(&node);
}

//Destructor
TernarySearchTree::~TernarySearchTree() {

}

//operators
TernarySearchTree& TernarySearchTree::operator=(const TernarySearchTree& other) {

  //TODO: swap idiom - create a copy of the node then swap the new one with it
  //do this first to provide exception safety
  TernarySearchTreeNode node;
  node = *other.root_node_;

  root_node_.reset(&node);
  num_nodes_ = other.num_nodes_;
  size_in_memory_ = other.size_in_memory_;

  return *this;
}

//Convert from string to c-style string
std::vector<char> TernarySearchTree::ConvertStringToCString(std::string str) {

  std::vector<char> wordCharacters (str.begin(), str.end());
  //remove newlines or tabs
  if (wordCharacters.back() == '\n' || wordCharacters.back() == '\t') {
    wordCharacters.pop_back();
  }
  wordCharacters.push_back('\0');
  return wordCharacters;
}

//Insert a node
TernarySearchTreeNode TernarySearchTree::InsertNode(TernarySearchTreeNode &currentNode, 
                                                    char character,
                                                    NodePosition position,
                                                    bool isRoot) {

  TernarySearchTreeNode newNode;
  newNode.set_character(character);

  if (!isRoot) {
    switch (position) {
    case NODE_POS_LEFT:
      currentNode.set_left_node(newNode);
      break;
    case NODE_POS_CENTRE:
      currentNode.set_centre_node(newNode);
      break;
    case NODE_POS_RIGHT:
      currentNode.set_right_node(newNode);
      break;
    default:
      break;
    }
  }

  return newNode;
}

//Insert a word
void TernarySearchTree::InsertWord(std::string word) {

  std::vector<char> characters = ConvertStringToCString(word);
  std::shared_ptr<TernarySearchTreeNode> currentNode = 0;
  bool isFirstCharacter = true;

  //Add each character to a node while traversing
  //Base case where there is no root node
  if (!root_node_) {

    for(std::vector<char>::iterator it = characters.begin(); it != characters.end(); ++it) {

      if (*it != '\0') {        
        //if it is the first character
        //root_node_ is equal to the address of new node
        if (isFirstCharacter) {
          std::cout << "HIHI";
          TernarySearchTreeNode node = InsertNode(*currentNode, *it, NODE_POS_CENTRE, true);
          root_node_.reset(&node);
          currentNode.reset(&node);
          isFirstCharacter = false;

        } else {
          TernarySearchTreeNode node = InsertNode(*currentNode, *it, NODE_POS_CENTRE, false);
          std::cout << std::endl << node.get_character();
          currentNode.reset(&node);
        }
      }
    }
    //If not base case, then we need to compare each character
  } else {
    currentNode = root_node_;
    for(std::vector<char>::iterator it = characters.begin(); it != characters.end(); ++it) {
      if (*it != '\0') {
        currentNode.reset(&SetNextNode(*currentNode, *it, *std::next(it, 1)));
      } else {
        currentNode->set_end_of_word(true);
      }
    }
  }
}

//Recursive function for obtaining/adding the next node when inserting a word
TernarySearchTreeNode TernarySearchTree::SetNextNode(TernarySearchTreeNode &currentNode, const char currentChar, const char nextChar) {

  //If characters match
  if (currentChar == currentNode.get_character()) {

    //if centre node exists
    if (currentNode.get_centre_node()) {
      return *(currentNode.get_centre_node());

      //Otherwise, create a new node and recall method on that node
    } else {

      //If not the end of the word, make a new node with the next letter
      if (nextChar != '\0') {
        return InsertNode(currentNode, nextChar, NODE_POS_CENTRE, false);

      } else {
        return currentNode;
      }
    }
    //If it is less, follow node on the left
  } else if (currentChar < currentNode.get_character()) {

    //if left node exists, recursive call
    if (currentNode.get_left_node()) {
      return SetNextNode(*(currentNode.get_left_node()), currentChar, nextChar);

      //Otherwise, create a new node and recall method on that node
    } else {
      return SetNextNode(InsertNode(currentNode, currentChar, NODE_POS_LEFT, false), currentChar, nextChar);
    }
    //Otherwise it is bigger, so take right path
  } else {

    //if right node exists, recursive call
    if (currentNode.get_right_node()) {
      return SetNextNode(*(currentNode.get_right_node()), currentChar, nextChar);

      //Otherwise, create a new node and recall method on that node
    } else {
      return SetNextNode(InsertNode(currentNode, currentChar, NODE_POS_RIGHT, false), currentChar, nextChar);
    }
  }
}

//Populate the TST from a word list/file
void TernarySearchTree::PopulateTreeFromTextFile(std::string fileName) {

  std::ifstream file;
  std::string line;
  file.open(fileName);

  if (file.is_open()) {
    //Assume text file has one word per line
    while (std::getline(file, line)) {
      InsertWord(line);
    }     
  }
}

//Search
bool TernarySearchTree::SearchForWord(std::string word) {
  return false;
}

int _tmain(int argc, _TCHAR* argv[])
{

  //Test
  TernarySearchTree tst;
  //Open file
  tst.PopulateTreeFromTextFile("simple.txt");

  //start at root and follow some paths
 std::cout << tst.get_root_node();


  /**std::vector<char> vec;
  vec.push_back('a');
  vec.push_back('c');
  std::vector<char>::iterator it = vec.begin();
  std::cout << *std::next(vec.begin(), 1);
  std::cout << (*it < 'c');
  it++;
  std::cout << *std::next(it, 0);
  std::cout <<  (*it < 'c');
  **/
  return 0;
}

对于节点:

/*TST node methods */
#include <iostream>
#include "ternary_search_tree_node.h"

/** ADD COPY CONSTRUCTOR*/
//Constructors
TernarySearchTreeNode::TernarySearchTreeNode() {

  character_ = '\0';
  end_of_word_ = false;
  left_node_ = nullptr;
  centre_node_ = nullptr;
  right_node_ = nullptr;
}

TernarySearchTreeNode::TernarySearchTreeNode(const TernarySearchTreeNode& other) {

  character_ = other.character_;
  end_of_word_ = other.end_of_word_;

  TernarySearchTreeNode leftNode;
  leftNode = *other.left_node_;
  left_node_.reset(&leftNode);

  TernarySearchTreeNode centreNode;
  centreNode = *other.centre_node_;
  centre_node_.reset(&centreNode);

  TernarySearchTreeNode rightNode;
  rightNode = *other.right_node_;
  right_node_.reset(&rightNode);
}

TernarySearchTreeNode::TernarySearchTreeNode(char character, bool end_of_word,
                                             TernarySearchTreeNode left_node, 
                                             TernarySearchTreeNode centre_node, 
                                             TernarySearchTreeNode right_node) {

  character_ = character;
  end_of_word_ = end_of_word;
  left_node_.reset(&left_node);
  centre_node_.reset(&centre_node);
  right_node_.reset(&right_node);
}

//Destructor
TernarySearchTreeNode::~TernarySearchTreeNode() {

  left_node_.reset();
  centre_node_.reset();
  right_node_.reset();

}

//operators
TernarySearchTreeNode& TernarySearchTreeNode::operator=(const TernarySearchTreeNode& other) {

  if (&other) {
    TernarySearchTreeNode leftNode;
    leftNode = *other.left_node_;
    TernarySearchTreeNode centreNode;
    centreNode = *other.centre_node_;
    TernarySearchTreeNode rightNode;
    rightNode = *other.right_node_;

    left_node_.reset(&leftNode);
    centre_node_.reset(&centreNode);
    right_node_.reset(&rightNode);

    character_ = other.character_;
    end_of_word_ = other.end_of_word_;
  }

  return *this;
}

//printing
std::ostream& operator<<(std::ostream& os, const TernarySearchTreeNode& obj)
{
  // write obj to stream
  char c = obj.get_character();
  bool b = obj.is_end_of_word();

  os << c << "\t is end of word: " << b;
  return os;
}

当我在调试模式(Visual Studios)下运行时,它能够设置根节点,但是当它输入第二个节点时,当 currentNode 在内部调用 .reset(&node) 时尝试删除“东西”时崩溃InsertWord 函数的 else 语句。我在复制构造函数或 operator= 方法或析构函数中做错了吗?它上面的 cout 行确实打印了正确的字母,因此看起来节点正在正确创建。

调试调用堆栈显示:

TernarySearchTree.exe!std::_Ref_count_base::_Decref() 第 118 行 C++ TernarySearchTree.exe!std::_Ptr_base::_Decref() 第 347 行 C++ TernarySearchTree.exe!std::shared_ptr::~shared_ptr() 第 624 行 C++ TernarySearchTree.exe!std::shared_ptr::reset() 第 649 行 C++ TernarySearchTree.exe!TernarySearchTreeNode::~TernarySearchTreeNode() 第 50 行 C++ TernarySearchTree.exe!TernarySearchTreeNode::`scalar 删除析构函数'(unsigned int) C++ TernarySearchTree.exe!std::_Ref_count::_Destroy() 第 161 行 C++ TernarySearchTree.exe!std::_Ref_count_base::_Decref() 第 120 行 C++ TernarySearchTree.exe!std::_Ptr_base::_Decref() 第 347 行 C++ TernarySearchTree.exe!std::shared_ptr::~shared_ptr() 第 624 行 C++ TernarySearchTree.exe!std::shared_ptr::reset() 第 649 行 C++ TernarySearchTree.exe!TernarySearchTreeNode::~TernarySearchTreeNode() 第 50 行 C++

TernarySearchTree.exe!TernarySearchTree::InsertWord(std::basic_string,std::allocator word) 第 105 行 C++ TernarySearchTree.exe!TernarySearchTree::PopulateTreeFromTextFile(std::basic_string,std::allocator 文件名)第 182 行 C++ TernarySearchTree.exe!wmain(int argc, wchar_t * * argv) 第 200 行 C++ TernarySearchTree.exe!__tmainCRTStartup() 第 533 行 C TernarySearchTree.exe!wmainCRTStartup() 第 377 行 C kernel32.dll!7592338a() 未知 [下面的帧可能不正确 和/或丢失,没有为 kernel32.dll 加载符号]
ntdll.dll!77599f72() 未知 ntdll.dll!77599f45() 未知

感谢您提供的任何帮助!如果还有什么需要我提供的,请告诉我(我正在阅读的文本文件中只有 corn这个词)。

【问题讨论】:

  • 你的问题可以浓缩吗?
  • 我将保留原始代码,但更具体地说,我将堆栈跟踪放在调试中(在底部)。你可以看到它显示了scalar deleting destructor,我认为通过查看其他问题意味着我没有定义复制构造函数....但是我定义了!
  • 为什么要使用共享指针?除非您希望能够在多个父树之间共享同一子树(这很不寻常),否则您应该改用 unique_ptr 使每个节点成为其后代的唯一所有者。
  • 我使用了共享指针,因为当我添加或遍历树时,我需要跟踪我所在的节点。我还能有 2 个指向同一个地址的唯一指针吗?就像我之前说的,我开始使用原始指针,当它向南时,我开始尝试其他东西......完全搞砸了我的代码 - 感谢您的帮助!

标签: c++ pointers visual-c++


【解决方案1】:

您的问题是您在 C++ 中使用 Java 样式。与 Java 中一切本质上都是指针不同,在 C++ 中,您必须考虑值、引用、指针和对象生命周期之间的区别。

这个功能不好:

TernarySearchTreeNode::TernarySearchTreeNode(char character, bool end_of_word,
                                             TernarySearchTreeNode left_node, 
                                             TernarySearchTreeNode centre_node, 
                                             TernarySearchTreeNode right_node) {

  character_ = character;
  end_of_word_ = end_of_word;
  left_node_.reset(&left_node);
  centre_node_.reset(&centre_node);
  right_node_.reset(&right_node);
}

您通过值获取TernarySearchTreeNode对象,然后将它们的地址放入shared_ptr。 shared_ptr 的意义在于获取动态分配的对象(使用new 创建的对象)的所有权,并在引用计数变为零时将其删除。上面的对象(left_node 等)是堆栈对象,将在函数结束时超出范围。当您将他们的地址放入shared_ptr 时,它稍后会尝试删除这些对象,但它们不再存在。

就建议如何解决这个问题而言,这里有很多假设是错误的。例如,一个子节点可以有多个父节点吗?复制节点真的有意义吗?

我暂时假设复制节点是有意义的,所以使用shared_ptr 是合理的。在这种情况下,我们可以从这里开始:

TernarySearchTreeNode TernarySearchTree::InsertNode(std::shared_ptr<TernarySearchTreeNode currentNode>, 
                                                    char character,
                                                    NodePosition position,
                                                    bool isRoot) {

  auto newNode = std::make_shared<TernarySearchTreeNode>();
  newNode->set_character(character);

  if (!isRoot) {
    switch (position) {
    case NODE_POS_LEFT:
      currentNode->set_left_node(newNode);

那么你所有的函数,比如set_left_node 也应该把std::shared_ptr&lt;TernarySearchNode&gt; 作为参数。你不应该调用reset(),它的存在是为了让shared_ptr 获得一个空闲指针的所有权(refcount == 1)。 shared_ptr 通过增加复制时的引用计数和析构函数中的取消引用来工作。当您取消引用指针然后获取地址时,您正在处理 shared_ptr。

【讨论】:

  • 所以我应该创建新节点(使用交换等)并重置这些节点?如果我为我的其他构造函数这样做(我实际上不认为我使用这个)会解决“标量删除析构函数”问题吗?感谢您的帮助,一旦我得到答案,将标记为正确:)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-11
  • 2012-09-27
  • 2015-03-30
  • 2023-03-11
  • 1970-01-01
  • 2018-08-13
  • 2018-02-11
相关资源
最近更新 更多