C++11神奇地删除了BST中的构造函数
C++11 Magically Deleted Constructor in BST
已解决!见下文
所以,我正试图通过做一些简单的数据结构并玩它们来学习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
作为成员(尽管编译器说"显式"…(删除了类的复制构造函数(,并注意到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)));
}
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节复制和移动c++11标准(草案n3337(的类对象(第9点(:
如果类X的定义没有明确声明移动构造函数,当且仅当
- X没有用户声明的复制构造函数
- X不具有用户声明的拷贝分配运算符
- X不具有用户声明的移动分配运算符
- X没有用户声明的析构函数,并且
- move构造函数不会被隐式定义为已删除
(我不确定move成员的隐含一代,并要求这个问题是肯定的(。使其可移动:
- 删除用户声明的析构函数,或者
- 定义移动构造函数和移动赋值运算符
注意:"Tree"的复制构造函数被隐式删除,因为字段"left"有一个已删除的复制构造函数
std::unique_ptr
没有复制构造函数
编译器可能不会就此发出警告(可能需要激活更多警告(,但这不会起作用:
template <typename T>
void Tree<T>::insert (Tree<T> child) {
// ...
left = &child;;
}
此代码获取一个临时变量的地址,并将其存储在unique_ptr
中。这是错误的。CCD_ 15用于存储指向已经分配有CCD_ 16的对象的指针。它的目的之一是在销毁时删除它们,这样就不会有任何内存泄漏。
这里,child
是一个临时的,在退出函数时将被销毁。这意味着left
将包含一个指向堆栈上任何内容的指针。这可能会导致随机损坏,并最终在Tree
对象被销毁时崩溃。
即使child
是一个引用(右值或左值(,你也不能假设它已经被分配了new
,因为可能不是这样(在你的代码中,从来都不是这样(,即使是这样,也可能对象已经在其他地方管理了(例如在另一个unique_ptr
中(,所以你不应该弄乱它。
相反,您想要的是为Tree
对象分配内存,并将其存储在left
:中
left = new Tree<T>(child);
您仍然需要将参数排序为insert
,这需要Tree
是可复制的(提示:使用右值引用:Tree<T>&& child
(,但这个问题更糟,因为您的编译器无法检测到这类错误。
您的专业构造函数
Tree(T arg);
重写编译器生成的默认构造函数。所以你必须自己包含它:
T() = default;
- "error: no matching function for call to"构造函数错误
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 选择要调用的构造函数
- 如何委托派生类使用其父构造函数?
- 构造函数正在调用一个使用当前类类型的函数
- 没有用于初始化C++中的变量模板的匹配构造函数
- 初始化具有非默认构造函数的std::数组项的更好方法
- 当从函数参数中的临时值调用复制构造函数时
- 在c++构造函数中使用随机字符串生成器
- 一对向量构造函数:初始值设定项列表与显式构造
- 从构造函数抛出异常时如何克服内存泄漏
- 我不明白为什么我声明一个空的内部结构并将其传递给构造函数
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 具有默认模板类型的默认构造函数的类型推导
- 使用dynamic_cast和构造函数时出错
- 获取链表上的"expected ‘)’ before ‘&’ token"到 BST 构造函数签名
- C++11神奇地删除了BST中的构造函数