解决!见下文
所以,我正在尝试通过做一些简单的数据结构并使用它们来学习C ++ 11。我使用原始指针和new
以及delete
对以下BST示例执行了类似的操作,并且它运行正常。然后我想以一种更加泄密的方式来做这件事。
// 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_ptr
s。
特别感谢BatchyX对问题的初步解释(隐式使用unique_ptr
作为成员(虽然编译器说“明确”......)删除了类的复制构造函数),并注意到确实,树仍然可以移动。
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)));
}
答案 0 :(得分:6)
std::unique_ptr
不可复制,任何包含unique_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;
这是一个用户声明的析构函数,来自 12.8复制和移动类对象(第9点)的c ++ 11标准(草案n3337):< / p>
如果类X的定义没有显式声明移动构造函数, 当且仅当
时,才会隐式声明一个默认值
- X没有用户声明的复制构造函数,
- X没有用户声明的复制赋值运算符
- X没有用户声明的移动赋值运算符
- X没有用户声明的析构函数和
- 移动构造函数不会被隐式定义为已删除。
(我不确定移动成员的隐含生成,并要求this问题确定。为了使它可以移动:
答案 1 :(得分:3)
注意:隐式删除'Tree'的复制构造函数,因为字段'left'具有已删除的复制构造函数
std::unique_ptr
没有复制构造函数
答案 2 :(得分:1)
编译器可能不会对此发出警告(可能需要激活更多警告),但这不起作用:
template <typename T>
void Tree<T>::insert (Tree<T> child) {
// ...
left = &child;;
}
此代码获取临时变量的地址并将其存储在unique_ptr
内。这是错的。 unique_ptr<A>
用于存储指向已使用new
分配的对象的指针。其目的之一是在销毁时删除它们,这样就不会有任何内存泄漏。
这里,child
是一个临时的,在退出函数时会被销毁。这意味着left
将包含指向堆栈上任何内容的指针。这可能会导致随机损坏,并最终在您的Tree
对象被销毁时崩溃。
即使child
是引用(rvalue或左值),也不能假设它已经被new
分配,因为可能不是这种情况(在你的代码中,它永远不是案例),即使它是,也许该对象已在其他地方管理(例如在另一个unique_ptr
),所以你不应该把它弄乱。
您想要的是为Tree
对象分配内存并将其存储在left
内:
left = new Tree<T>(child);
您仍然需要将insert
的参数整理为需要Tree
可复制的提示(提示:使用右值引用代替:Tree<T>&& child
),但这个问题更糟糕,因为你的编译器无法检测到这类错误。
答案 3 :(得分:0)
您的专业构造函数
Tree(T arg);
覆盖编译器生成的默认构造函数。所以你必须自己包括它:
T() = default;