您的A 类不遵循Rule of Three:
三法则(也称为三巨头法则或三巨头)是 C++(C++11 之前)中的一条经验法则,它声称如果一个类定义了以下一项(或多项)它可能应该明确定义所有三项:
这三个函数是特殊的成员函数。如果使用其中一个函数而没有首先由程序员声明,它将由编译器隐式实现,默认语义是对类的所有成员执行上述操作。
-
析构函数 – 调用所有对象类类型成员的析构函数
-
复制构造函数 – 从复制构造函数参数的相应成员构造对象的所有成员,调用对象的类类型成员的复制构造函数,并对所有非类类型(例如, int 或指针) 数据成员
-
复制赋值操作符——从赋值操作符参数的对应成员中赋值对象的所有成员,调用对象类类型成员的复制赋值操作符,对所有非类类型进行普通赋值(例如 int 或指针)数据成员。
三法则声称,如果其中之一必须由程序员定义,则意味着编译器生成的版本在一种情况下不适合类的需求,并且可能不适合其他情况也是如此。“三法则”一词是 Marshall Cline 在 1991 年创造的。
对这条规则的修改是,如果类的设计方式是资源获取即初始化 (RAII) 用于其所有(非平凡)成员,则析构函数可能未定义(也称为法则大二)。这种方法的一个现成示例是使用智能指针而不是普通指针。
由于隐式生成的构造函数和赋值运算符只是简单地复制所有类数据成员(“浅拷贝”),因此应该为封装复杂数据结构或具有外部引用的类定义显式复制构造函数和复制赋值运算符,例如指针,如果您需要复制类成员指向的对象。 如果默认行为(“浅拷贝”)实际上是预期行为,那么显式定义虽然是多余的,但将是“自记录代码”,表明这是一个意图而不是疏忽。
你需要添加一个拷贝构造函数和一个拷贝赋值运算符(你的析构函数需要使用delete[]而不是delete):
class A
{
private:
int *array;
int size;
public:
A(int s)
: size(s), array(new int[s])
{
fprintf(stderr, "Allocated %p\n", array);
}
A(const A &src)
: size(src.size), array(new int[src.size])
{
std::copy(src.array, src.array + src.size, array);
fprintf(stderr, "Allocated %p, Copied from %p\n", array, src.array);
}
~A()
{
fprintf(stderr, "Deleting %p\n", array);
delete[] array;
}
A& operator=(const A &rhs)
{
A tmp(rhs);
std::swap(array, tmp.array);
std::swap(size, tmp.size);
return *this;
}
};
既然你提到emplace_back(),那就意味着你使用的是C++11或更高版本,这意味着你还应该处理Rule of Five的移动语义:
随着 C++11 的出现,三规则可以扩展到五规则,因为 C++ 11 实现了移动语义,允许目标对象从临时对象中获取(或窃取)数据。以下示例还显示了新的移动成员:移动构造函数和移动赋值运算符。因此,对于五人法则,我们有以下特殊成员:
- 析构函数
- 复制构造函数
- 移动构造函数
- 复制赋值运算符
- 移动赋值运算符
存在类可能需要析构函数但不能明智地实现复制和移动构造函数以及复制和移动赋值运算符的情况。例如,当基类不支持后面的四大成员时,就会发生这种情况,但派生类的构造函数分配内存供自己使用。[需要引用]在 C++11 中,这可以通过显式指定五个成员来简化默认成员。
您应该在上面的代码中添加一个移动构造函数和一个移动赋值运算符:
class A
{
private:
int *array;
int size;
public:
A(int s)
: size(s), array(new int[s])
{
fprintf(stderr, "Allocated %p\n", array);
}
A(const A &src)
: size(src.size), array(new int[src.size])
{
std::copy(src.array, src.array + src.size, array);
fprintf(stderr, "Allocated %p, Copied from %p\n", array, src.array);
}
A(A &&src)
: size(0), array(nullptr)
{
std::swap(array, src.array);
std::swap(size, src.size);
fprintf(stderr, "Moved %p, Replaced with %p\n", array, src.array);
}
~A()
{
fprintf(stderr, "Deleting %p\n", array);
delete[] array;
}
A& operator=(const A &rhs)
{
A tmp(rhs);
std::swap(array, tmp.array);
std::swap(size, tmp.size);
return *this;
}
A& operator=(A &&rhs)
{
std::swap(array, rhs.array);
std::swap(size, rhs.size);
return *this;
}
};
否则,您应该争取Rule of Zero:
R. Martinho Fernandes 提议将以上所有内容简化为 C++ 的 0 规则(主要用于 C++11 及更高版本)。 0 规则规定,如果您指定任何默认成员,则您的类必须专门处理单个资源。此外,它必须定义所有默认成员来处理该资源(或酌情删除默认成员)。因此,这些类必须遵循上述 5 规则。资源可以是任何东西:分配的内存、文件描述符、数据库事务等。
任何其他类不得直接分配任何资源。此外,它们必须省略默认成员(或通过= default 将所有成员显式分配为默认成员)。 任何资源都应该通过使用单一资源类作为成员/局部变量来间接使用。这让这些类从成员变量的联合中继承默认成员,从而自动转发所有底层资源联合的可移动性/可复制性。由于 1 个资源的所有权仅由 1 个成员变量拥有,因此构造函数中的异常不会因 RAII 而泄漏资源。完全初始化的变量将调用其析构函数,而未初始化的变量一开始就不能拥有任何资源。
由于大多数类不将所有权作为他们唯一关心的问题,因此大多数类可以省略默认成员。这就是 0 规则得名的地方。
完全消除您的手动数组并改用std::vector:
class A
{
private:
std::vector<int> array;
public:
A(int s)
: array(s)
{
}
};
无需显式定义复制/移动构造函数、复制/移动赋值运算符或析构函数,因为编译器提供的默认实现会自动为您调用vector的相应功能。