设计数据的正确方法 class 处理多态数据

Correct way to design data class to handle polymorphic data

我需要设计一个结构数据来保存指向 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 中的代码由于双重删除而崩溃。我怎样才能正确处理所有这些用例。

我想到的一种方法是,使用 private 隐藏 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() 我能够避免分段错误,用户可以自由地创建他喜欢的结构数据对象。

有没有更好的方法来设计这些类?

Code in block 1 and block 2 crashes due to double deletion. How can i handle all these use cases properly.

遵循规则 3(如果您想支持高效的移动操作,则遵循规则 5):

if a class defines one (or more) of the following it should probably explicitly define all three:

  • destructor
  • copy constructor
  • copy assignment operator

您忽略了实现自定义复制赋值运算符。使用默认复制赋值运算符导致双重删除。


此外,永远不要像在块 1 中那样将指向自动变量的指针分配给 Data::baseData

Data 的析构函数将删除此指针,这会导致未定义的行为。

此外,永远不要删除 Data::baseData 拥有的指针,除非你打算用其他东西替换它。

为避免不小心执行这些操作,我建议将 Data::baseData 声明为私有,正如您已经考虑过的那样。


Is there any better way to design these classes?

是的。永远不要使用指向拥有内存的裸指针。请改用 std::unique_ptr

你的问题是你试图自己管理所有权。相反,您可以使用 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 的实际类型