它看起来像不完整的代码。应该是这样的
template <class T>
class treenode
因为vector<treenode<int>*> child 是模板类型vector 带有类型参数treenode<int>* 和treenode 必须是模板才能允许模板参数<int>。我也质疑这段代码的合理性。如果它描述了一个包含T类型数据的树节点(可能是int),那么
template <class T>
class treenode
{
public:
T data;
vector<treenode<T>*> child;
treenode(T data) // (or T&? or const T&? or T&&?)
{
this ->data = data;
}
声明template <class T> class treenode 上下文中的vector<treenode*> 是指向treenode<T> 的指针向量。
现在考虑以下代码。我向treenode 添加了一些有用的东西来展示如何分配内存。为避免混淆,新类型 Data 将用作 T 而不是 int。
#include <iostream>
#include <vector>
// using namespace std;
template <class T>
class treenode
{
public:
T data; // with using `namespace std;` this can be a problem,
// because there is `std::data`
// treenode<T> is class containing this, so we can refer it
// just as treenode
std::vector<treenode*> child;
treenode(const T& d) : data(d)
// if you use data as argument name,
// you are shadowing treenode::data member
{
}
void add_node(const T& d)
{
// here we create a pointer with value returned by new
// treenode was initialized by d within new expression.
child.emplace_back(new treenode{d});
}
~treenode() {
// vector contained raw pointers we allocated memory for.
// this avoids leaks.
for(auto& p : child)
delete p;
}
};
template <class T>
struct Data {
T value;
Data(T v) : value(v) {std::cout << "Hello, data = " << v << std::endl;}
~Data() {std::cout << "Bye, data = " << value << std::endl;}
};
int main()
{
// this is legal after C++17, template parameters can be deducted
treenode a = {Data{42}};
// It's same as this C++11 declaration
// treenode<Data<int>> a = {42};
a.add_node(34);
return 0;
}
我们可以使用智能指针,避免使用 new
#include <iostream>
#include <vector>
#include <memory>
template <class T>
class treenode
{
public:
T data;
std::vector<std::unique_ptr<treenode>> child;
treenode(const T& d) : data(d) {}
void add_node(const T& d)
{
child.emplace_back(std::make_unique<treenode>(d));
}
~treenode() { }
// no action needed, pointers clean up after themselves!
};
你能猜出它会做什么吗?
Hello, data = 42
Bye, data = 42
Hello, data = 34
Bye, data = 34
// a is getting destroyed.
Bye, data = 34
Bye, data = 42
一些数据死在那里。那是因为这个设置创建了一些临时的 T = Data,Data 被复制或移动了,我们没有看到它。没关系,如果treenode 的T 是int。对于复杂类型,如果我们的数据没有正确的复制\移动操作,这可能是一个问题。让我们至少添加构造函数。
template <class T>
struct Data {
T value;
Data(const T& v) : value(v)
{std::cout << "Hello, data = " << v << std::endl;}
// move constructor
Data(Data&& v) : value(std::move(v.value))
{std::cout << "Moved data = " << v.value << std::endl; v.value = T{}; }
// copy constructor
Data(const Data& v) : value(v.value)
{ std::cout << "Copied data = " << v.value << std::endl; }
~Data() {std::cout << "Bye, data = " << value << std::endl;}
};
现在我们可以看到复制操作了:
Hello, data = 42
Copied data = 42
Bye, data = 42
Hello, data = 34
Copied data = 34
Bye, data = 34
Bye, data = 34
Bye, data = 42
对于简单的“平面”类型,这可以很好,复制和移动操作对它们来说是相等的。但是对于某些容器来说,创建我们永远不会再次使用的临时对象然后复制分配的内存是昂贵的。同意“移动”操作将分配资源的所有权转移到目标对象并修改源,因此它不会释放它们。之后源对象将变得无效。我们必须在 treenode 类中添加对 T 的移动和复制的支持。
template <class T>
class treenode
{
public:
T data;
std::vector<std::unique_ptr<treenode>> child;
treenode(const T& d) : data(d) {}
treenode(T&& d) : data(std::move(d)) {}
void add_node(const T& d)
{
child.emplace_back(std::make_unique<treenode>(d));
}
void add_node(T&& d)
{
child.emplace_back(std::make_unique<treenode>(std::move(d)));
}
~treenode() {
}
};
现在一切都加起来了:
Hello, data = 42 // we created Data(42)
Moved data = 42 // its value was moved
Bye, data = 0 // it was destroyed.
Hello, data = 34
Moved data = 34
Bye, data = 0
Bye, data = 34 // a is being destroyed
Bye, data = 42
拥有两个add_node 实现是多余的。在某些情况下,如果涉及多个参数,可能会导致声明数量呈指数级增长。我们只需要使用单个perfect forwarding。
template <class V = T>
void add_node(V&& d) {
child.emplace_back(std::make_unique<treenode>(std::forward<V>(d)));
}
这将导致这样的操作:
Data c {63};
a.add_node(c);
创建副本:
Hello, data = 63
Copied data = 63
最后需要为树节点定义一个默认构造函数,以及一个移动构造函数,以完成其对Rule of 5 的设计。复制构造函数可能正确也可能不正确,这取决于我们是允许创建树的副本,仅创建节点值的副本,还是根本不允许。
template <class T>
class treenode
{
public:
T data;
std::vector<std::unique_ptr<treenode>> child;
treenode(const T& d) : data(d) {}
treenode(T&& d) : data(std::move(d)) {}
treenode() : data() {}
// treenode(treenode&& other):
// data(std::move(other.data)), child(std::move(other.child)) {}
treenode(treenode&& other) = default;
// would not copy children, std::unique_ptr is not copyable
treenode(const treenode& other) : data(other.data) {}
// it's default because we move all members.
treenode& operator=(treenode&& other) = default;
// copies data's value, doesn't alter children
treenode& operator=(const treenode& other)
{ this->data = other.data; return *this; }
template <class V = T>
void add_node(V&& d) {
child.emplace_back(std::make_unique<treenode>(std::forward<V>(d)));
}
~treenode() = default;
};
数据也需要它的特殊成员:
template <class T>
struct Data {
T value;
Data(const T& v) : value(v)
{ std::cout << "Hello, data = " << v << std::endl;}
Data(Data&& v) : value(std::move(v.value))
{ std::cout << "Moved data = " << v.value << std::endl; v.value = T{}; }
Data(const Data& v) : value(v.value)
{ std::cout << "Copied data = " << v.value << std::endl; }
Data() : T() {}
~Data() {std::cout << "Bye, data = " << value << std::endl;}
Data& operator=(Data&& other) = default;
Data& operator=(const Data& other) = default;
};