我正在尝试扩展我创建的类模板的功能。以前它允许您使用任何类型的键值对,但前提是您在编译时知道数组的大小。它看起来像这样:
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 调试器对此进行了测试,发现它很好地存储了第一个键值对,然后它尝试将下一个键值对存储在下一个索引中,但数据无处可去。并且调试器只显示每个数组中的一个槽。当我尝试计算我认为存储在第二个索引中的数据时,我得到一个非常小的数字(试图存储 float 据类型),而不是我试图存储的数据。
我知道使用 vector 来完成这个可能是值得的。然而,这是我在学校 C++ 课上完成的作业的扩展,我这样做的目的是尝试完成它,并了解这样做可能会导致问题的原因,因为这对我来说是显而易见的方法以我目前所掌握的知识。
编辑:用于添加键值对的代码:
// 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;
}
编辑:调用 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++ - 为什么我的类模板动态分配的数组属性只能存储一项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33426713/