将类型安全代码与运行时决策相结合

时间:2017-05-08 17:17:30

标签: c++ templates class-design

我正在重写一些现有代码 - 之前,所有答案信息都存储在内存中的字符串数组中。根据数据类型,数据在各个地方进行了转换。下面是我瞄准的设置的快速模拟。基本上你有一些问题 - 存储在数据库中的答案结构取决于数据类型。一般来说,我避免处理void *,并将它们转换为适当的类型 - 但我找不到更好的解决方案,允许我运行通用代码(通过lambdas),或者如果数据类型已知则具体。在这种情况下,模板化的类不会有帮助,因为所有答案都需要存储在同一个向量中(因为某些算法会根据预定义的规则应用于所有答案)。

感谢任何建议。

#include <vector>
#include <memory>


struct AddressData
{
    wchar_t Line1[50];
    wchar_t Line2[50];
    long CountrySeqNo;

    AddressData()
    {
        memset(this, 0, sizeof(*this));
    };
};

struct GenericData
{
    wchar_t value[200];

    GenericData()
    {
        memset(this, 0, sizeof(*this));
    };
};

enum class DataType
    : short
{
    GENERIC,
    ADDRESS
};

class AnswerBase
{
protected:
    const void* const data;
    const DataType dataType;

protected:
    AnswerBase(const DataType datatype, const void* const _data)
        : dataType(datatype), data(data)
    {
        if (data == nullptr)
            throw std::exception("Data may not be initialized as NULL");
    };

public:
    /*
        Some generic methods here that would apply logic by means of lambdas etc - these would be overwritten in the derived classes
    */

    template<typename T> const T& GetData() { static_assert(false, "The given type is not supported"); };

    template<> 
    const GenericData& GetData() 
    { 
        if (DataType::GENERIC != dataType)
            throw std::exception("The requested type does not match the value that initialised data");

        return *static_cast<const GenericData* const>(data);
    };
    template<>
    const AddressData& GetData()
    {
        if (DataType::ADDRESS != dataType)
            throw std::exception("The requested type does not match the value that initialised data");

        return *static_cast<const AddressData* const>(data);
    };


};


class AddressAnswer
    : public AnswerBase
{
public:
    AddressAnswer()
        : AnswerBase(DataType::ADDRESS, &answer)
    {
    };

protected:
    AddressData answer;
};


class GenericAnswer
    : public AnswerBase
{
public:
    GenericAnswer()
        : AnswerBase(DataType::GENERIC, &answer)
    {
    };

protected:
    GenericData answer;
};


int main()
{
    std::vector<std::shared_ptr<AnswerBase>> answers;
    answers.push_back(std::make_shared<GenericAnswer>());
    answers.push_back(std::make_shared<AddressAnswer>());


    // In some parts of code - interact with generic methods without needing to check the underlying data type
    // ....
    // ....

    // In parts of code where we know we are dealing with a given type - like saving to a DB


    auto val1 = answers[0]->GetData<GenericData>().value;
    auto val2 = answers[1]->GetData<AddressData>().Line1;

    // this will give a runtime failure
    //auto val3 = answers[0]->GetData<AddressData>().Line1;


    return 0;
}

1 个答案:

答案 0 :(得分:1)

variant是干净的方法。将其存储在父级中。

或者,在父级中提供variant<A,B> GetData()。现在访问被封装在返回的变体中。父级存储数据。

或者,提供virtual variant<A,B> GetData() = 0。子类型返回相关变体中的数据AB

或者,写下virtual A* GetA() = 0; virtual B* GetB() = 0;。然后可能会编写一个名为GetData<T>的模板方法,以便GetData<A>()调用GetA等。

或者,写下virtual A* Get(tag_t<A>) = 0; virtual B* Get(tag_t<B>)=0;,其中

template<class T>
struct tag_t {
  using type=T;
  constexpr tag_t(){}
};
template<class T>
constexpr tag_t<T> tag{};

是用于调度的标记。现在,您可以通过执行Get(tag<AddressData>)来调用正确的虚拟接口。

在这些虚拟情况下,数据存储在派生类型中。