C++ DoublyLinkedList 单元测试智能初始化失败,但通过 new 初始化成功
C++ DoublyLinkedList unit test fails with smart initialization but succeeds with initialization via new
我正在用 C++ 编写 DoublyLinkedList
,我为此实现编写的测试之一以一种奇怪的方式失败了。
我创建了一个StubClass
来填充我的DoublyLinkedList
,定义如下:
class StubClass {
public:
int data;
StubClass(int _data) {
data = _data;
}
StubClass() {
data = -1;
}
};
我正在使用 C++ 的 Google 测试框架。我有以下夹具:
class DoublyLinkedListTest : public ::testing::Test {
public:
DoublyLinkedList<StubClass> doublyLinkedList;
};
通过以下测试:
TEST_F(DoublyLinkedListTest, LargePushAndPopRoutineWithSmartInitializedData) {
int repetitions = 12;
for (int i = 0; i < repetitions; i++) {
StubClass stub(i);
doublyLinkedList.push_back(&stub);
}
for (int i = 0; i < repetitions; i++) {
ASSERT_EQ(i, doublyLinkedList.pop_front()->data);
}
}
问题是 DoublyLinkedList
对象中的所有 StubClass
对象都将它们的数据字段设置为 11。因此测试失败,因为从前面弹出的第一个项目应该有它的数据字段设置为 0。测试的输出 运行:
Value of: doublyLinkedList.pop_front()->data
Actual: 11
Expected: i
Which is: 0
奇怪的是,以下测试确实通过了:
TEST_F(DoublyLinkedListTest, LargePushAndPopRoutine) {
int repetitions = 12;
for (int i = 0; i < repetitions; i++) {
doublyLinkedList.push_back(new StubClass(i));
}
for (int i = 0; i < repetitions; i++) {
ASSERT_EQ(i, doublyLinkedList.pop_front()->data);
}
}
此测试使用 new
关键字实例化 StubClass
而不是失败测试中的智能初始化。
我对 C++ 的经验不多,只是在学习复制和移动构造函数之类的东西。我怀疑我可能在失败的测试中犯了一个愚蠢的 C++ 初学者错误。
为了完整起见,我将 DoublyLinkedList
class 的实现放在下面:
DoublyLinkedList.h
#ifndef PROJECT_DOUBLYLINKEDLIST_H
#define PROJECT_DOUBLYLINKEDLIST_H
#include "DoublyLinkedNode.h"
template<class T>
class DoublyLinkedList {
private:
DoublyLinkedNode<T> *head = nullptr;
DoublyLinkedNode<T> *tail = nullptr;
public:
bool hasOneItem() {
return head == tail && !isEmpty();
}
void push_front(T* data) {
DoublyLinkedNode <T> *newNode = new DoublyLinkedNode<T>();
newNode->data = data;
if (isEmpty()) {
head = newNode;
tail = newNode;
}
else {
newNode->setNext(head);
head = newNode;
}
}
void push_back(T* data) {
DoublyLinkedNode <T> *newNode = new DoublyLinkedNode<T>();
newNode->data = data;
if (isEmpty()) {
head = newNode;
tail = newNode;
}
else {
newNode->setPrev(tail);
tail = newNode;
}
}
T* pop_front() {
if (isEmpty()) {
return nullptr;
}
DoublyLinkedNode<T> *oldHead = head;
if (hasOneItem()) {
head = nullptr;
tail = nullptr;
return oldHead->data;
}
head->next->prev = nullptr;
head = head->next;
return oldHead->data;
}
T* pop_back() {
if (isEmpty()) {
return nullptr;
}
DoublyLinkedNode<T> *oldTail = tail;
if (hasOneItem()) {
head = nullptr;
tail = nullptr;
return oldTail->data;
}
tail->prev->next = nullptr;
tail = tail->prev;
return oldTail->data;
}
bool isEmpty() {
return !head && !tail;
}
};
#endif //PROJECT_DOUBLYLINKEDLIST_H
DoublyLinkedNode.h
#ifndef PROJECT_DOUBLYLINKEDNODE_H
#define PROJECT_DOUBLYLINKEDNODE_H
template<class T>
class DoublyLinkedNode {
public:
DoublyLinkedNode *prev = nullptr;
DoublyLinkedNode *next = nullptr;
T *data = nullptr;
/**
* Will also set 'prev' on the nextNode
*/
void setNext(DoublyLinkedNode *nextNode) {
next = nextNode;
if (nextNode) {
nextNode->prev = this;
}
}
void setPrev(DoublyLinkedNode *prevNode) {
prevNode->setNext(this);
}
};
#endif //PROJECT_DOUBLYLINKEDNODE_H
发生这种情况是因为您创建的对象是 for 循环的本地对象,然后使用这些对象的地址将它们推入列表。这些对象中的每一个都在相应的循环迭代终止后被销毁。在你的例子中,它们都是在堆栈上的相同地址创建的,这就是为什么它们似乎都用设置为 11 的数据字段初始化的原因。事实上,你的列表包含指向用于创建这些相同内存位置的指针对象,最后写入此位置的是一个数据字段设置为 11 的对象。
然而,当您使用new
时,这些对象分配在不同位置的堆上。这就是为什么一切都按您预期的那样工作。在这种情况下,你有内存泄漏,因为你的容器没有删除这些对象。
我正在用 C++ 编写 DoublyLinkedList
,我为此实现编写的测试之一以一种奇怪的方式失败了。
我创建了一个StubClass
来填充我的DoublyLinkedList
,定义如下:
class StubClass {
public:
int data;
StubClass(int _data) {
data = _data;
}
StubClass() {
data = -1;
}
};
我正在使用 C++ 的 Google 测试框架。我有以下夹具:
class DoublyLinkedListTest : public ::testing::Test {
public:
DoublyLinkedList<StubClass> doublyLinkedList;
};
通过以下测试:
TEST_F(DoublyLinkedListTest, LargePushAndPopRoutineWithSmartInitializedData) {
int repetitions = 12;
for (int i = 0; i < repetitions; i++) {
StubClass stub(i);
doublyLinkedList.push_back(&stub);
}
for (int i = 0; i < repetitions; i++) {
ASSERT_EQ(i, doublyLinkedList.pop_front()->data);
}
}
问题是 DoublyLinkedList
对象中的所有 StubClass
对象都将它们的数据字段设置为 11。因此测试失败,因为从前面弹出的第一个项目应该有它的数据字段设置为 0。测试的输出 运行:
Value of: doublyLinkedList.pop_front()->data
Actual: 11
Expected: i
Which is: 0
奇怪的是,以下测试确实通过了:
TEST_F(DoublyLinkedListTest, LargePushAndPopRoutine) {
int repetitions = 12;
for (int i = 0; i < repetitions; i++) {
doublyLinkedList.push_back(new StubClass(i));
}
for (int i = 0; i < repetitions; i++) {
ASSERT_EQ(i, doublyLinkedList.pop_front()->data);
}
}
此测试使用 new
关键字实例化 StubClass
而不是失败测试中的智能初始化。
我对 C++ 的经验不多,只是在学习复制和移动构造函数之类的东西。我怀疑我可能在失败的测试中犯了一个愚蠢的 C++ 初学者错误。
为了完整起见,我将 DoublyLinkedList
class 的实现放在下面:
DoublyLinkedList.h
#ifndef PROJECT_DOUBLYLINKEDLIST_H
#define PROJECT_DOUBLYLINKEDLIST_H
#include "DoublyLinkedNode.h"
template<class T>
class DoublyLinkedList {
private:
DoublyLinkedNode<T> *head = nullptr;
DoublyLinkedNode<T> *tail = nullptr;
public:
bool hasOneItem() {
return head == tail && !isEmpty();
}
void push_front(T* data) {
DoublyLinkedNode <T> *newNode = new DoublyLinkedNode<T>();
newNode->data = data;
if (isEmpty()) {
head = newNode;
tail = newNode;
}
else {
newNode->setNext(head);
head = newNode;
}
}
void push_back(T* data) {
DoublyLinkedNode <T> *newNode = new DoublyLinkedNode<T>();
newNode->data = data;
if (isEmpty()) {
head = newNode;
tail = newNode;
}
else {
newNode->setPrev(tail);
tail = newNode;
}
}
T* pop_front() {
if (isEmpty()) {
return nullptr;
}
DoublyLinkedNode<T> *oldHead = head;
if (hasOneItem()) {
head = nullptr;
tail = nullptr;
return oldHead->data;
}
head->next->prev = nullptr;
head = head->next;
return oldHead->data;
}
T* pop_back() {
if (isEmpty()) {
return nullptr;
}
DoublyLinkedNode<T> *oldTail = tail;
if (hasOneItem()) {
head = nullptr;
tail = nullptr;
return oldTail->data;
}
tail->prev->next = nullptr;
tail = tail->prev;
return oldTail->data;
}
bool isEmpty() {
return !head && !tail;
}
};
#endif //PROJECT_DOUBLYLINKEDLIST_H
DoublyLinkedNode.h
#ifndef PROJECT_DOUBLYLINKEDNODE_H
#define PROJECT_DOUBLYLINKEDNODE_H
template<class T>
class DoublyLinkedNode {
public:
DoublyLinkedNode *prev = nullptr;
DoublyLinkedNode *next = nullptr;
T *data = nullptr;
/**
* Will also set 'prev' on the nextNode
*/
void setNext(DoublyLinkedNode *nextNode) {
next = nextNode;
if (nextNode) {
nextNode->prev = this;
}
}
void setPrev(DoublyLinkedNode *prevNode) {
prevNode->setNext(this);
}
};
#endif //PROJECT_DOUBLYLINKEDNODE_H
发生这种情况是因为您创建的对象是 for 循环的本地对象,然后使用这些对象的地址将它们推入列表。这些对象中的每一个都在相应的循环迭代终止后被销毁。在你的例子中,它们都是在堆栈上的相同地址创建的,这就是为什么它们似乎都用设置为 11 的数据字段初始化的原因。事实上,你的列表包含指向用于创建这些相同内存位置的指针对象,最后写入此位置的是一个数据字段设置为 11 的对象。
然而,当您使用new
时,这些对象分配在不同位置的堆上。这就是为什么一切都按您预期的那样工作。在这种情况下,你有内存泄漏,因为你的容器没有删除这些对象。