问题
SOLVED! See below
So, I'm trying to learn C++11 by doing some simple data structures and playing around with them. I did something similar to the following BST example using raw pointers and new and delete and it worked fine. Then I wanted to do it in a way that was more leak-safe.
// 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;
}
I don't undersatnd the error I'm getting from clang++, however.
$ 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.
Why does it say I explicitly deleted the constructor when I declared the struct? I even defined a constructor explicitly! Also, any comments on scoping/ownership failure would be appreciated. I'm pretty sure this won't work the way I did it anyways.
Solution
The following link from MSDN clarified how one can use unique_ptrs.
Special thanks to BatchyX for his initial explanation of the problem (using a unique_ptr as a member implicitly (though the compiler says "explicitly"...) deletes the copy constructor for the class), and for noting that indeed Tree is still movable.
Something mentioned in that MSDN article is that std::move() returns an rvalue of its argument.
Here is the suitably modified code (excluding the obviously modified declarations). Note that there might be some optimizations possible still by using std::forward, but this at least seems to compile and run correctly.
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)));
}
回答1:
std::unique_ptr is not copyable, and any class that contains a unique_ptr is also not copyable, meaning struct Tree is not copyable. The argument to:
void Tree<T>::insert (Tree<T> child) {
is taking its argument by value. And:
template <typename T>
void Tree<T>::insert (T arg) {
Tree<T> child (arg);
this->insert(child);
}
requires the copy constructor. To correct this, make struct Tree moveable.
Note Tree is not moveable (in contrast to BatchyX's comment) due to the presence of:
~Tree () = default;
which is a user-declared destructor and from section 12.8 Copying and moving class objects (point 9) of the c++11 standard (draft n3337):
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
- X does not have a user-declared copy constructor,
- X does not have a user-declared copy assignment operator,
- X does not have a user-declared move assignment operator,
- X does not have a user-declared destructor, and
- the move constructor would not be implicitly defined as deleted.
(I was uncertain about the implicit generation of the move members and asked this question to be certain). To make it moveable either:
- remove the user-declared destructor, or
- define a move constructor and move assignment operator
回答2:
note: copy constructor of 'Tree' is implicitly deleted because field 'left' has a deleted copy constructor
std::unique_ptr doesn't have a copy-constructor
回答3:
The compiler may not warn you on this (maybe you need to activate more warnings), but this will not work:
template <typename T>
void Tree<T>::insert (Tree<T> child) {
// ...
left = &child;;
}
This code takes the address of a temporary variable and stores it inside a unique_ptr. This is wrong. unique_ptr<A> is for storing pointers to objects that have been allocated with new. One of its purpose is to delete them on destruction so you don't have any memory leak.
Here, child is a temporary that will be destroyed when exiting the function. That mean left will contain a pointer pointing to whatever lies on the stack. This may lead to random corruptions, and ultimately crashes when your Tree object will be destroyed.
Even if child is a reference (rvalue or lvalue), you cannot assume that it has been allocated with new, because it may not be the case (In your code, it is never the case), and even if it is was, maybe the object is already managed elsewhere (e.g. in another unique_ptr) so you shouldn't mess with it.
What you want instead is to allocate memory for a Tree object and store it inside left:
left = new Tree<T>(child);
You still need to sort out the argument to insert which requires Tree to be copyable (hint: use a rvalue references instead: Tree<T>&& child), but this problem is even worse because your compiler cannot detect these kind of errors.
回答4:
Your specialized constructor
Tree(T arg);
overrides the compiler-generated default-constructor. So you must include it on your own:
T() = default;
来源:https://stackoverflow.com/questions/16875230/c11-magically-deleted-constructor-in-bst