2017-10-04 37 views
-1

私は、基本データ型へのポインタを保持する構造体データを設計する必要があります。ユーザーは、このデータ構造体のオブジェクトを簡単に作成し、メモリ管理の問題の多くを処理せずに回り越えることができます。多型データを扱うデータクラスを設計する正しい方法

構造がほとんど作成されていません。正しい処理方法を提案してください。

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のコードは、二重削除のためにクラッシュします。これらのユースケースをすべて適切に処理するにはどうすればよいですか。

私が考えている方法の1つは、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()では、セグメンテーションフォールトを避けることができ、ユーザーは自分が好きな構造化データのオブジェクトを自由に作成できます。

これらのクラスを設計する方法はありますか?

答えて

1

あなたの問題は、あなた自身で所有権を管理しようとしていることです。代わりに、unique_ptrタイプを使用して明示的な所有権管理を使用できます。

(私たちは、後に表示されますcreateDataType方法+)に使用したものと同じタイプの定義を仮定:

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"); 
    } 
} 
:私たちは今、そうのように、私たちのオブジェクトを作成するためのファクトリを使用している

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; } 
}; 

お知らせ次のように

その後、あなたはあなたの管理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

ブロック1とブロック2のコードは、二重削除のためにクラッシュします。これらのユースケースをすべて適切に処理するにはどうすればよいですか。 3のルールに従うことによって

(または5のルールを使用すると、効率的な移動操作をサポートしたい場合):クラスが定義されている場合

それをする必要があり、次はおそらく明示の1つ(またはそれ以上)すべての3つを定義します。

  • デストラクタ
  • コピーコンストラクタ
  • コピー代入演算子

あなたはカスタムコピー代入演算子の実装を怠っています。デフォルトのコピー代入演算子を使用すると、二重削除が行われます。あなたがここにブロックで1

Dataのデストラクタは、未定義の動作になり、このポインタを、削除されます行うよう


また、Data::baseDataに自動変数へのポインタを割り当てることはありません。

また、Data::baseDataが所有するポインタは、別のものに置き換えない限り削除しないでください。

これらを偶然にしないように、私はすでに考えているようにData::baseDataを非公開と宣言することをお勧めします。


これらのクラスを設計するために任意のより良い方法はありますか?

はい。所有メモリへの裸のポインタを使用しないでください。代わりにstd::unique_ptrを使用してください。

関連する問題