问题描述
我需要将相同类别的对象添加到向量中:
I need to add objects of the same class to a vector:
#include <vector>
#include <cstdio>
class A {
int *array;
int size;
public:
A(int s) {
array = new int[size = s];
fprintf(stderr, "Allocated %p\n", (void*)array);
}
~A() {
fprintf(stderr, "Deleting %p\n", (void*)array);
delete array;
}
};
int main() {
std::vector<A> v;
for (int n = 0; n < 10; n++) {
fprintf(stderr, "Adding object %d\n", n);
v.push_back(A(10 * n));
//v.emplace_back(10 * n);
}
return 0;
}
当我运行该程序时,它在产生以下输出后崩溃:
When I run this program, it crashes after producing the following output:
Adding object 0
Allocated 0x146f010
Deleting 0x146f010
Adding object 1
Allocated 0x146f050
Deleting 0x146f010
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x000000000146f010 ***
似乎在添加第一个对象时调用了第0个对象的析构函数。当我使用emplace_back而不是push_back时,甚至更陌生:
It seems that the destructor of the 0th object is called when adding the 1st object. Even stranger is when I use emplace_back instead of push_back:
Adding object 0
Allocated 0x1644030
Adding object 1
Allocated 0x1644080
Deleting 0x1644030
Adding object 2
Allocated 0x1644100
Deleting 0x1644030
Deleting 0x1644080
Adding object 3
Allocated 0x1644160
Adding object 4
Allocated 0x1644270
Deleting 0x1644030
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000001644030 ***
有人可以解释为什么会这样,以及正确的方法吗?在Linux下使用的编译器是g ++ 4.7.2,但在Mac OS X下使用clang 7.3.0也会得到相同的行为。
Can someone explain why this is happening, and the correct way to do this? Compiler used was g++ 4.7.2 under Linux, but I'm also getting the same behavior with clang 7.3.0 under Mac OS X.
推荐答案
您的 A
类不遵循:
- 析构函数
- 复制构造函数
- 复制赋值运算符
这三个函数是特殊的成员函数。如果使用其中一个函数而没有先由程序员声明,则编译器将使用默认语义对编译器进行隐式实现,该默认语义是对类的所有成员执行上述操作。
These three functions are special member functions. If one of these functions is used without first being declared by the programmer it will be implicitly implemented by the compiler with the default semantics of performing the said operation on all the members of the class.
- 析构函数 –调用对象的所有类类型成员的析构函数
- 复制构造函数 > –从副本构造函数的参数的相应成员构造对象的所有成员,调用对象的类类型成员的副本构造函数,并对所有非类类型(例如int或指针)数据成员进行简单分配
- 复制赋值运算符 –从赋值运算符参数的相应成员中分配对象的所有成员,调用对象的类类型成员的副本赋值运算符,并对所有非类类型(例如int或指针)的数据成员进行简单分配。
- Destructor – Call the destructors of all the object's class-type members
- Copy constructor – Construct all the object's members from the corresponding members of the copy constructor's argument, calling the copy constructors of the object's class-type members, and doing a plain assignment of all non-class type (e.g., int or pointer) data members
- Copy assignment operator – Assign all the object's members from the corresponding members of the assignment operator's argument, calling the copy assignment operators of the object's class-type members, and doing a plain assignment of all non-class type (e.g. int or pointer) data members.
三则规则声称,如果程序员必须定义其中之一,则意味着在一种情况下编译器生成的版本不适合该类的需求,因此三元规则一词是1991年由马歇尔·克莱恩(Marshall Cline)创造的。
The Rule of Three claims that if one of these had to be defined by the programmer, it means that the compiler-generated version does not fit the needs of the class in one case and it will probably not fit in the other cases either. The term "Rule of three" was coined by Marshall Cline in 1991.
对此规则的修正是如果该类的设计方式是对其所有(非平凡的)成员使用资源获取是初始化(RAII),则析构函数可能未定义(也称为两大法则)。这种方法的现成示例是使用智能指针而不是普通指针。
An amendment to this rule is that if the class is designed in such a way that Resource Acquisition Is Initialization (RAII) is used for all its (nontrivial) members, the destructor may be left undefined (also known as The Law of The Big Two). A ready-to-go example of this approach is the use of smart pointers instead of plain ones.
因为隐式生成的构造函数和赋值运算符只需复制所有类数据成员(浅复制),如果需要复制类成员指向的对象,则应为封装复杂数据结构或具有外部引用(如指针)的类定义显式复制构造函数和复制赋值运算符。 / strong>如果默认行为(浅表复制)实际上是预期的行为,那么一个明确的定义(尽管多余)将是自我记录代码,表明这是一个意图,而非疏忽。
Because implicitly-generated constructors and assignment operators simply copy all class data members ("shallow copy"), one should define explicit copy constructors and copy assignment operators for classes that encapsulate complex data structures or have external references such as pointers, if you need to copy the objects pointed to by the class members. If the default behavior ("shallow copy") is actually the intended one, then an explicit definition, although redundant, will be a "self-documenting code" indicating that it was an intention rather than an oversight.
您需要添加一个副本构造函数和一个副本赋值运算符(并且您的析构函数需要使用 delete []
而不是 delete
):
You need to add a copy constructor and a copy assignment operator (and your destructor needs to use delete[]
instead of 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或更高版本,这意味着您还应该处理:
Since you mention emplace_back()
, that means you are using C++11 or later, which means you should also deal with move semantics of the Rule of Five:
- destructor
- copy构造函数
- 移动构造函数
- 复制赋值运算符
- 移动赋值运算符
- destructor
- copy constructor
- move constructor
- copy assignment operator
- move assignment operator
存在类可能需要析构函数的情况,但无法明智地实现复制和移动构造函数以及复制和移动赋值运算符。例如,当基类不支持后面的四大成员时,却发生了这种情况,而派生类的构造函数为自己使用分配了内存。[需要引用]在C ++ 11中,可以通过显式指定五个来简化此过程。
Situations exist where classes may need destructors, but cannot sensibly implement copy and move constructors and copy and move assignment operators. This happens, for example, when the base class does not support these latter Big Four members, but the derived class's constructor allocates memory for its own use.[citation needed] In C++11, this can be simplified by explicitly specifying the five members as default.
您应该在上述代码中添加一个move构造函数和一个move赋值运算符:
You should add a move constructor and a move assignment operator to the above code:
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;
}
};
否则,您应该争取代替:
Otherwise, you should strive for the Rule of Zero instead:
任何其他类都不能直接分配任何资源。此外,他们必须省略默认成员(或通过 = default
明确地将所有成员分配给default)。 任何资源都应通过将单一资源类用作成员/局部变量来间接使用。这样,此类可以从成员变量的并集继承默认成员,从而自动转发所有基础资源并集的可移动性/可复制性。由于1个资源的所有权完全由1个成员变量拥有,因此构造函数中的异常不会由于RAII而泄漏资源。完全初始化的变量的析构函数称为&未初始化的变量不可能拥有任何资源。
Any other class must not allocate any resources directly. Furthermore, they must omit the default members (or explicitly assign all of them to default via = default
). Any resources should be used indirectly by using the single-resource classes as member/local variables. This lets such classes inherit the default members from the union of member variables, thereby auto-forwarding the movability/copyability of the union of all underlying resource. Since ownership of 1 resource is owned by exactly 1 member variable, exceptions in the constructor cannot leak resources due to RAII. Fully initialized variables will have their destructors called & uninitialized variables could not have owned any resources to begin with.
由于大多数类都不以所有权为唯一考虑因素,因此大多数类可以省略默认成员。这是0规则的名称。
Since the majority of classes don't deal with ownership as their sole concern, the majority of classes can omit the default members. This is where the rule-of-0 gets its name.
完全取消您的手动数组,并使用 std :: vector
代替:
Eliminate your manual array altogether and use a std::vector
instead:
class A
{
private:
std::vector<int> array;
public:
A(int s)
: array(s)
{
}
};
无需显式定义复制/移动构造函数,复制/移动赋值运算符或析构函数,因为编译器提供的默认实现会自动为您调用 vector
的相应功能。
No need to explicitly define a copy/move constructor, copy/move assignment operator, or a destructor, because the default implementations provided by the compiler will automatically call the corresponding functionality of the vector
for you.
这篇关于C ++向矢量添加对象会破坏早期的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!