作为一项学术练习,我创建了一个自定义向量实现,我希望支持非pod类型的复制。
我希望容器支持存储不提供默认构造函数的元素。
当我为向量保留内存,然后推送一个元素(它管理自己的资源,并实现了一个复制和赋值运算符——我暂时忽略了move构造函数)时,我在使用该类型的复制交换习惯用法时遇到了问题。
因为交换发生在一个仍然是未初始化内存的类型上,所以在交换之后,为临时调用的析构函数将试图释放一些未初始化的数据,这些数据当然会爆炸。
我能看到一些可能的解决方案。一种是确保所有非pod类型都实现一个默认构造函数,并在集合中的每个元素上调用该构造函数(placementnew)。我不喜欢这个主意,因为它看起来既浪费又麻烦。
另一种方法是在进行交换之前,将容器中该类型空间的内存memset为0(这样临时的将为null,调用析构函数将不会出错)。不过,这对我来说有点棘手,我不确定是否有更好的选择(请参阅下面的代码以获取示例)。在为一堆元素调用reserve后,你也可以将所有保留空间memset为0,但这可能会浪费时间。
是否有关于如何为std::vector实现这一点的文档,因为调用reserve不会为分配的元素调用构造函数,而resize会(对于没有实现默认构造函数的类型,可以将构造的临时作为第二个参数传递给调用)
下面是一些可以运行的代码来演示这个问题,我省略了实际的矢量代码,但原理保持不变。
#include <iostream>
#include <cstring>
// Dumb example type - not something to ever use
class CustomType {
public:
CustomType(const char* info) {
size_t len = strlen(info) + 1;
info_ = new char[len];
for (int i = 0; i < len; ++i) {
info_[i] = info[i];
}
}
CustomType(const CustomType& customType) {
size_t len = strlen(customType.info_) + 1;
info_ = new char[len];
for (int i = 0; i < len; ++i) {
info_[i] = customType.info_[i];
}
}
CustomType& operator=(CustomType customType) {
swap(*this, customType);
return *this;
}
void swap(CustomType& lhs, CustomType& rhs) {
std::swap(lhs.info_, rhs.info_);
}
~CustomType() {
delete[] info_;
}
char* info_;
};
int main() {
CustomType customTypeToCopy("Test");
// Mimics one element in the array - uninitialised memory
char* mem = (char*)malloc(sizeof(CustomType));
// Cast to correct type (would be T for array element)
CustomType* customType = (CustomType*)mem;
// If memory is cleared, delete[] of null has no effect - all good
memset(mem, 0, sizeof(CustomType));
// If the above line is commented out, you get malloc error - pointer
// being freed, was not allocated
// Invokes assignment operator and copy/swap idiom
*customType = customTypeToCopy;
printf("%sn", customType->info_);
printf("%sn", customTypeToCopy.info_);
return 0;
}
如有任何信息/建议,我们将不胜感激!
解决了!
感谢@Brian和@Nim帮助我理解分配(复制/交换)有效的用例。
为了实现我想要的,我只需要更换
*customType = customTypeToCopy;
带有
new (customType) CustomType(customTypeToCopy);
调用复制构造函数而不是赋值运算符!
谢谢!
您不使用复制和交换进行构建。
为了解决以下问题,可以使用复制和交换赋值:赋值的左侧是一个已经初始化的对象,因此在将右侧的状态复制或移动到其中之前,它需要释放所拥有的资源;但是,如果复制或移动构造由于抛出异常而失败,我们希望保持原始状态。
如果你正在进行构造而不是赋值——因为目标未初始化——那么通过复制和交换解决的问题就不存在。您只需调用带有placement new的构造函数。如果成功了,那就太好了。如果抛出异常而失败,则该语言保证已经构建的任何子对象都会被销毁,并且您只需让异常向上传播;在失败的情况下,目标的状态将与以前相同:未初始化。