2

解决了!见下文

所以,我试图通过做一些简单的数据结构并玩弄它们来学习 C++11。我使用原始指针做了类似于以下 BST 示例的操作new,并且delete效果很好。然后我想以一种更安全的方式来做这件事。

// tree.cpp
// 
// 

#include <iostream>
#include <memory>

/* DECLARATIONS */
template <typename T>
struct Tree {
  // members
  T data;
  std::unique_ptr<Tree<T> > left;
  std::unique_ptr<Tree<T> > right;
  // methods
  Tree (T arg);
  ~Tree () = default;
  void insert (Tree<T> child);
  void insert (T arg);
  void print (void);
};

template <typename T>
Tree<T>::Tree (T arg) {
  data = arg;
  left = nullptr;
  right = nullptr;
}

template <typename T>
void Tree<T>::insert (Tree<T> child) {
  if (child.data < data) {
    if (left) {
      left->insert(child);
    } else {
      left = &child;
    }
  } else {
    if (right) {
      right->insert(child);
    } else {
      right = &child;
    }
  }
}

template <typename T>
void Tree<T>::insert (T arg) {
  Tree<T> child (arg);
  this->insert(child);
}

template <typename T>
void Tree<T>::print (void) {
  if (left) {
    left->print();
  }
  std::cout << data;
  if (right) {
    right->print();
  }
}

int main (void) {
  Tree<int> root (0);
  root.insert(3);
  root.insert(-3);
  root.insert(-2);
  root.insert(2);
  root.insert(11);
  root.print();
  return 0;
}

但是,我并不了解我从 clang++ 得到的错误。

$ clang++ -std=c++11 tree.cpp

tree_new.cpp:50:16: error: call to deleted constructor of 'Tree<int>'
  this->insert(child);
               ^~~~~
tree_new.cpp:66:8: note: in instantiation of member function 'Tree<int>::insert'         requested here
   root.insert(3);
   ^
tree_new.cpp:10:8: note: function has been explicitly marked deleted here
struct Tree {
       ^
tree_new.cpp:18:24: note: passing argument to parameter 'child' here
  void insert (Tree<T> child);
                       ^
tree_new.cpp:34:20: error: call to deleted constructor of 'Tree<int>'
  left->insert(child);
               ^~~~~
tree_new.cpp:50:9: note: in instantiation of member function 'Tree<int>::insert'requested here
  this->insert(child);
        ^
tree_new.cpp:66:8: note: in instantiation of member function 'Tree<int>::insert' requested here
  root.insert(3);
       ^
tree_new.cpp:10:8: note: function has been explicitly marked deleted here
struct Tree {
       ^
tree_new.cpp:18:24: note: passing argument to parameter 'child' here
  void insert (Tree<T> child);
                       ^
2 errors generated.

为什么它说我在声明时明确删除了构造函数struct?我什至明确定义了一个构造函数!此外,我们将不胜感激有关范围界定/所有权失败的任何评论。我很确定这无论如何都不会像我那样工作。

解决方案

MSDN的以下链接阐明了如何使用unique_ptrs.

特别感谢 BatchyX 对问题的初步解释(unique_ptr隐式使用 a 作为成员(尽管编译器说“显式”...)删除了类的复制构造函数),并注意到 Tree 确实仍然是可移动的。

该 MSDN 文章中提到的内容是std::move()返回其参数的右值。

这是经过适当修改的代码(不包括明显修改的声明)。请注意,使用 std::forward 可能仍然可以进行一些优化,但这至少似乎可以正确编译和运行。

template <typename T>
void Tree<T>::insert (std::unique_ptr<Tree<T> >&& pchild) {
  if (pchild->data < data) {
    if (left) {
      // recurse, but must match on the rvalue signature
      left->insert(std::move(pchild));
    } else {
      // invokes the move constructor for left instead of its copy constructor
      left = std::move(pchild);
    }
  } else {
    if (right) {
      right->insert(std::move(pchild));
    } else {
      right = std::move(pchild);
    }
  }
}

template <typename T>
void Tree<T>::insert (T arg) {
  // what is inside the insert(...) is an rvalue.
  this->insert(std::unique_ptr<Tree<T> >(new Tree<T> (arg)));
}
4

4 回答 4

6

std::unique_ptr是不可复制的,任何包含 aunique_ptr的类也是不可复制的,意思struct Tree是不可复制的。论据:

void Tree<T>::insert (Tree<T> child) {

是通过价值来接受它的论点。和:

template <typename T>
void Tree<T>::insert (T arg) {
  Tree<T> child (arg);
  this->insert(child);
}

需要复制构造函数。要更正此问题,请使其struct Tree可移动。


由于存在以下内容,注释不可移动Tree(与BatchyX的评论相反):

~Tree () = default;

这是一个用户声明的析构函数,来自c++11 标准(草案 n3337)的第12.8 节复制和移动类对象(第 9 点):

如果类 X 的定义没有显式声明移动构造函数,当且仅当

  • X 没有用户声明的复制构造函数,
  • X 没有用户声明的复制赋值运算符,
  • X 没有用户声明的移动赋值运算符,
  • X 没有用户声明的析构函数,并且
  • 移动构造函数不会被隐式定义为已删除。

(我不确定移动成员的隐含生成,并要求确定这个问题)。要使其可移动:

  • 删除用户声明的析构函数,或
  • 定义移动构造函数和移动赋值运算符
于 2013-06-01T17:28:49.343 回答
3

注意:'Tree' 的复制构造函数被隐式删除,因为字段 'left' 具有已删除的复制构造函数

std::unique_ptr没有复制构造函数

于 2013-06-01T17:28:31.303 回答
1

编译器可能不会对此发出警告(也许您需要激活更多警告),但这不起作用:

template <typename T>
void Tree<T>::insert (Tree<T> child) {
  // ...
  left = &child;;
}

此代码获取临时变量的地址并将其存储在unique_ptr. 这是错误的。 unique_ptr<A>用于存储指向已分配对象的指针new。它的目的之一是在销毁时删除它们,这样您就不会发生任何内存泄漏。

这里,child是一个临时的,将在退出函数时被销毁。这意味着left将包含一个指向堆栈上任何内容的指针。这可能会导致随机损坏,并最终在您的Tree对象被销毁时崩溃。

即使child是一个引用(右值或左值),你也不能假设它已经被分配了new,因为它可能不是这种情况(在你的代码中,它从来不是这种情况),即使它是,也可能是对象已经在其他地方(例如在 another unique_ptr)管理,所以你不应该弄乱它。

你想要的是为一个Tree对象分配内存并将其存储在里面left

left = new Tree<T>(child);

您仍然需要整理出insert需要Tree可复制的参数(提示:改用右值引用:)Tree<T>&& child,但这个问题更糟,因为您的编译器无法检测到此类错误。

于 2013-06-01T18:17:37.603 回答
0

您的专业构造函数

Tree(T arg);

覆盖编译器生成的默认构造函数。所以你必须自己包含它:

T() = default;
于 2013-06-01T17:27:10.150 回答