为什么我的类模板的动态分配数组属性只能存储一个项
Why is the dynamically allocated array attribute of my class template only able to store one item?
我正在尝试扩展我创建的类模板的功能。以前,它允许您使用任何类型的键值对,但前提是您在编译时知道数组的大小。它看起来是这样的:
template <typename K, typename V, int N>
class KVList {
size_t arraySize;
size_t numberOfElements;
K keys[N];
V values[N];
public:
KVList() : arraySize(N), numberOfElements(0) { }
// More member functions
}
我希望能够将其用于运行时决定的动态元素数量,所以我将代码更改为:
template <typename K, typename V>
class KVList {
size_t arraySize;
size_t numberOfElements;
K* keys;
V* values;
public:
KVList(size_t size) : numberOfElements(0) {
arraySize = size;
keys = new K[size];
values = new V[size];
}
~KVList() {
delete[] keys;
keys = nullptr;
delete[] values;
values = nullptr;
}
// More member functions
}
新构造函数有一个参数,该参数的大小将用于KVList。它仍然在0处启动numberOfElements,因为这两种用法都会将KVList启动为空,但它确实将arraySize设置为size参数的值。然后,它为键和值的数组动态分配内存。添加的析构函数会释放这些数组的内存,然后将它们设置为nullptr。
它编译并运行,但它只存储我试图添加到它的第一个键和第一个值。两者中都有一个成员函数,可以向数组添加一个键值对。我用Visual Studio 2015调试器测试了这一点,注意到它将第一个键值对存储得很好,然后它试图将下一个键值对保存在下一个索引中,但数据不在任何位置。调试器在每个数组中只显示一个插槽。当我试图对我认为存储在第二个索引中的数据进行排序时,我得到了一个非常小的数字(试图存储的是浮点数据类型),而不是我试图存储的数据。
我知道使用向量来实现这一点可能是值得的。然而,这是我在学校C++课上完成的一项作业的扩展,我这样做的目标是努力完成它,并了解以这种方式做可能会导致问题的原因,因为就我目前所知,这对我来说是显而易见的。
EDIT:用于添加键值对的代码:
// Adds a new element to the list if room exists and returns a reference to the current object, does nothing if no room exists
KVList& add(const K& key, const V& value) {
if (numberOfElements < arraySize) {
keys[numberOfElements] = key;
values[numberOfElements] = value;
numberOfElements++;
}
return *this;
}
EDIT:调用add()的代码:
// Temp strings for parts of a grade record
string studentNumber, grade;
// Get each part of the grade record
getline(fin, studentNumber, subGradeDelim); // subGradeDelim is a char whose value is ' '
getline(fin, grade, gradeDelim); // gradeDelim is a char whose value is 'n'
// Attempt to parse and store the data from the temp strings
try {
data.add(stoi(studentNumber), stof(grade)); // data is a KVList<size_t, float> attribute
}
catch (...) {
// Temporary safeguard, will implement throwing later
data.add(0u, -1);
}
用于测试显示信息的代码:
void Grades::displayGrades(ostream& os) const {
// Just doing first two as test
os << data.value(0) << std::endl;
os << data.value(1);
}
用于测试的主cpp文件中的代码:
Grades grades("w6.dat");
grades.displayGrades(cout);
w6.dat:的内容
1022342 67.4
1024567 73.5
2031456 79.3
6032144 53.5
1053250 92.1
3026721 86.5
7420134 62.3
9762314 58.7
6521045 34.6
输出:
67.4
-1.9984e+18
问题(或至少其中一个)是您的pastebin中的这一行:
data = KVList<size_t, float>(records);
这条看似无辜的线路做了很多事情。由于数据已经存在,默认情况下会构建您输入Grades构造函数主体的实例,因此这将做三件事:
- 它将使用构造函数在右侧构造一个KVList
- 它将调用复制赋值运算符,并将我们在步骤1中构造的内容赋值给数据
- 右侧的对象被破坏
你可能在想:什么复印作业操作员,我从来没有写过。编译器会自动为您生成它。实际上,在C++11中,使用显式析构函数自动生成拷贝赋值运算符(正如您所做的那样)是不推荐的;但它仍然存在。
问题是编译器生成的复制赋值运算符不适合您。所有成员变量都是琐碎的类型:整数和指针。所以他们只是抄了一遍。这意味着在第2步之后,类已经以最明显的方式被复制了。这反过来意味着,对于一个简短的例子,左边和右边都有一个对象,它们都有指向内存中同一位置的指针。当步骤3启动时,右侧对象实际上会继续并删除内存。因此,数据只剩下指向随机垃圾内存的指针。写入这个随机内存是未定义的行为,所以你的程序可能会做(不一定是确定性的)奇怪的事情。
(老实说)显式资源管理类的编写方式有很多问题,这里无法涵盖太多问题。我认为在Accelerated C++这本非常优秀的书中,它将带你了解这些问题,其中有整整一章涵盖了如何正确编写这样一个类的每一个细节。
- C++是否有一个容器,每个类型最多存储一个对象
- C++存储一个 const&to 临时
- 设计具有变体字段的文本类型类,其中可以存储一个或三个对象
- 是否可以存储一个在静态容器中包含unique_ptr的对象
- 编译器是否可以从全局变量中读取两次,而不是存储一个局部变量
- 在c++中存储一个对象或不存储对象的首选方式是什么
- 如何使结构一次只存储一个变量?C++
- 在std ::设置中存储一个指针的指针是安全的吗?
- 我想使用C++映射,以存储一个矩形id的4个坐标,任何人都可以举一个例子吗
- 存储一个函数的迭代结果,并在被另一个函数调用时使用它们
- 为什么要为存储一个整数(c++)声明一个双精度数组
- 在数组的每个元素中存储一个值:将C do{}while循环转换为MIPS asm
- 如何使用 cin 一次存储一个值
- 我应该通过引用、值或ptr来存储一个完全封装的成员吗
- 如何让用户键入一个值而不是存储一个常量值
- 在对象中存储一个std::函数,该函数包括std::占位符
- 如何使数组中的每个数字都存储一个字符串,就像constchar*argv[]所做的那样
- 为什么我的类模板的动态分配数组属性只能存储一个项
- 我如何存储一个文件在变量中使用GMP在c++
- 如何存储一个指针到一个位置在char *缓冲区,发送给一个函数,检索值