2017-10-04 12 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의 코드가 이중 삭제로 인해 충돌합니다. 이러한 모든 유스 케이스를 어떻게 적절하게 처리 할 수 ​​있습니까?

내가 생각한 한 가지 방법은 비공개를 사용하여 baseData 포인터를 숨기고 setBaseData(const BaseData& o)struct Data에 메소드를 제공하는 것입니다. setBaseData와

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

는() 나는 세그먼트 오류와 사용자를 방지 할 수 있어요 이제까지 그가 좋아하는 구조체 데이터의 개체를 만들 무료입니다.

이러한 클래스를 설계하는 더 좋은 방법이 있습니까?

답변

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

을 그리고 당신은 항상 같은 동적를 사용하여 baseData의 실제의 형태를 확인할 수는 같이 캐스트 addData

1

블록 1과 블록 2의 코드가 이중 삭제로 인해 충돌합니다. 이러한 모든 유스 케이스를 어떻게 적절하게 처리 할 수 ​​있습니까? 3의 규정에 따라

(또는 5의 규칙을 사용하면 효율적으로 이동 작업을 지원하려는 경우) : 클래스가 정의하는 경우

는 IT를해야 다음 아마도 명시 적 중 하나 (또는 ​​그 이상) 정의 세 :

  • 소멸자
  • 복사 생성자
  • 복사 할당 연산자

사용자 지정 사본 할당 연산자를 구현하지 않았습니다. 기본 사본 할당 연산자를 사용하면 이중 삭제가 발생합니다. 당신이 블록 1

Data의 소멸자가 정의되지 않은 동작 결과이 포인터를 삭제 여기처럼


또한, Data::baseData에 자동 변수에 대한 포인터를 할당하지 않습니다.

또한 다른 것으로 바꾸지 않는 한 Data::baseData이 소유 한 포인터를 삭제하지 마십시오.

우연히 이러한 일을하지 않으려면 이미 고려한 것처럼 Data::baseData을 비공개로 선언하는 것이 좋습니다.


는 이러한 클래스를 설계 할 수있는 더 좋은 방법이 있나요?

예. 소유 한 메모리에 벌거 벗은 포인터를 사용하지 마십시오. 대신 std::unique_ptr을 사용하십시오.