设计数据类以处理多态数据的正确方法
我需要设计一个结构数据,它将持有指向基本数据类型的指针。用户应该能够轻松创建此数据结构的对象并传递,而无需处理大量的内存管理问题。设计数据类以处理多态数据的正确方法
我已经创建了几个结构,请建议正确的方法来处理它。
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中的代码由于双重删除而崩溃。我如何正确处理所有这些用例。
我想到的一种方法是,使用private隐藏baseData指针并向用户setBaseData(const BaseData& o)
提供方法struct Data
。
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) });
}
}
而且你总是可以检查使用相同的动态的baseData的实际类型转换为addData
块1和块2中的代码由于双重删除而崩溃。我如何正确处理所有这些用例。
通过以下3规则(或5的规则,如果你想支持高效的移动操作):
如果一个类定义的下列它可能应该明确一个(或多个)定义所有三:
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
您已经忽略了实现自定义复制赋值操作符。使用默认的复制赋值运算符会导致双重删除。
而且,从来没有像你在块1
的Data
的析构函数将删除此指针,这会导致不确定的行为在这里做一个指针赋给一个自动变量Data::baseData
。
此外,除非你打算用其他东西替换它,否则永远不要删除Data::baseData
所拥有的指针。
为了避免意外执行这些操作,我建议您声明Data::baseData
专用,因为您已经考虑过了。
有没有什么更好的方法来设计这些类?
是的。永远不要使用裸指针来拥有内存。改为使用std::unique_ptr
。