我需要设计一个结构数据,它将保存指向Base数据类型的指针。用户应该能够轻松创建此数据结构的对象并传递,而无需处理太多内存管理问题。
我创建了一些结构,请建议正确的处理方法。
struct BaseData {
enum DataType { DATATYPE_1, DATATYPE_2 };
virtual ~BaseData() { cout << "BaseData Dtor" << endl; }
};
struct DataType1 : BaseData {
virtual ~DataType1() { cout << "DataType1 Dtor" << endl; }
};
struct DataType2 : BaseData {
virtual ~DataType2() { cout << "DataType2 Dtor" << endl; }
};
struct Data {
Data() { cout << "Data Ctor" << endl; }
Data(const Data& o) {
if (o.baseData->type == BaseData::DATATYPE_1) {
baseData = new DataType1;
*(static_cast<DataType1*>(baseData)) = *(static_cast<DataType1*>(o.baseData));
}
else if (o.baseData->type == BaseData::DATATYPE_2) {
baseData = new DataType2;
*(static_cast<DataType2*>(baseData)) = *(static_cast<DataType2*>(o.baseData));
}
}
virtual ~Data() {
cout << "Data Dtor" << endl;
delete baseData; //here it results in segmentation fault if object is created on stack.
baseData = NULL;
}
BaseData* baseData;
};
vector <Data> vData;
void addData(const Data& d) { cout << "addData" << endl; vData.push_back(d); }
客户端代码如下所示。
int main()
{
{
DataType1 d1;
d1.type = BaseData::DATATYPE_1;
Data data;
data.baseData = &d1;
addData(data);
}
{
BaseData* d2 = new DataType2;
d2->type = BaseData::DATATYPE_2;
Data data;
data.baseData = d2;
addData(data);
delete d2;
d2 = NULL;
}
{
Data data;
data.baseData = new DataType1;
static_cast<DataType1*>(data.baseData)->type = BaseData::DATATYPE_1;
addData(data);
delete data.baseData;
data.baseData = NULL;
}
}
块1 和块 2 中的代码由于双重删除而崩溃。我怎样才能正确处理所有这些用例。
我想到的一种方法是,使用私有隐藏baseData指针,并在struct Data
中为用户提供setBaseData(const BaseData& o)
的方法。
void setBaseData(const BaseData& o) {
cout << "setBaseData" << endl;
if (o.type == BaseData::DATATYPE_1) {
baseData = new DataType1;
*(static_cast<DataType1*>(baseData)) = static_cast<const DataType1&>(o);
}
else if (o.type == BaseData::DATATYPE_2) {
baseData = new DataType2;
*(static_cast<DataType2*>(baseData)) = static_cast<const DataType2&>(o);
}
}
使用 setBaseData(),我能够避免分割错误,用户可以自由创建他喜欢的结构数据对象。
有没有更好的方法来设计这些类?
你的问题是你试图自己管理所有权。相反,您可以使用unique_ptr
类型使用显式所有权管理。
假设您使用的类型定义相同(+ 我们稍后会看到的createDataType方法):
struct BaseData {
enum DataType { DATATYPE_1, DATATYPE_2 };
virtual ~BaseData() { cout << "BaseData" << endl; }
static std::unique_ptr<BaseData> createDataType(DataType type);
};
struct DataType1 : BaseData {
virtual ~DataType1() { cout << "DataType1" << endl; }
};
struct DataType2 : BaseData {
virtual ~DataType2() { cout << "DataType2" << endl; }
};
请注意,我们现在使用工厂来创建对象,如下所示:
static std::unique_ptr<BaseData> BaseData::createDataType(BaseData::DataType type) {
switch(type) {
case BaseData::DATATYPE_1:
return std::make_unique<DataType1>();
case BaseData::DATATYPE_2:
return std::make_unique<DataType2>();
default:
throw std::runtime_error("ERR");
}
}
然后,应按如下方式声明管理Data
对象:
struct Data {
Data()
: baseData(nullptr) {}
Data(std::unique_ptr<BaseData> data)
: baseData(std::move(data)) {}
Data(Data && rhs)
: baseData(std::move(rhs.baseData)) {}
std::unique_ptr<BaseData> baseData;
};
现在我们可以编写干净、清晰和安全的代码,如下所示:
vector<Data> vData;
void addData(Data&& d) {
if (dynamic_cast<DataType1 *>(d.baseData.get()) != nullptr)
cout << "Adding DataType 1" << endl;
else if (dynamic_cast<DataType2 *>(d.baseData.get()) != nullptr)
cout << "Adding DataType 2" << endl;
vData.push_back(std::move(d));
}
int main()
{
{ // Option 1: Create base data somewhere, create data from it
auto baseData = createDataType(BaseData::DATATYPE_1);
Data data { std::move(baseData) };
addData(std::move(data));
}
{ // Option 2: Create data directly knowing the base data type
Data data { createDataType(BaseData::DATATYPE_2) };
addData(std::move(data));
}
{ // Option 3: Create data and add it to the vector
addData({ createDataType(BaseData::DATATYPE_1) });
}
}
而且,您始终可以使用与addData
中相同的动态强制转换来检查 baseData 的实际类型
1 和块 2 中的代码由于双重删除而崩溃。我怎样才能正确处理所有这些用例。
遵循 3 法则(如果您希望支持高效的移动操作,则遵循 5 法则):
如果一个类定义了以下一项(或多项),则可能应该显式定义所有三个:
- 破坏者
- 复制构造函数
- 复制赋值运算符
您忽略了实现自定义复制分配运算符。使用默认复制赋值运算符会导致双重删除。
此外,切勿像在块 1 中那样将指向自动变量的指针分配给Data::baseData
。
Data
的析构函数将删除此指针,这会导致未定义的行为。
此外,切勿删除Data::baseData
拥有的指针,除非您要将其替换为其他内容。
为了避免意外地做这些,我建议像你已经考虑的那样声明Data::baseData
私有的。
有没有更好的方法来设计这些类?
是的。永远不要使用裸指针指向拥有的内存。请改用std::unique_ptr
。