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

时间:2017-10-04 21:58:58

标签: c++ struct segmentation-fault polymorphism class-design

我需要设计一个结构数据,它将保存指向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指针,并为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(),我可以避免分段错误,用户可以自由地创建他喜欢的struct Data对象。

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

2 个答案:

答案 0 :(得分:1)

  

块1中的代码和块2由于双重删除而崩溃。如何正确处理所有这些用例。

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

  

如果一个类定义了以下一个(或多个),它应该明确定义所有三个:

     
      
  •   
  • 复制构造函数
  •   
  • 复制分配操作员
  •   

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

此外,永远不要将指向自动变量的指针指定给Data::baseData,就像在块1中一样。

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

此外,永远不要删除Data::baseData所拥有的指针,除非您要用其他内容替换它。

为避免意外地执行此操作,我建议您已经考虑将Data::baseData声明为私有。

  

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

是。不要使用裸指针来拥有内存。请改用std::unique_ptr

答案 1 :(得分:1)

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