将类型安全代码与运行时决策相结合
Combining typesafe code with runtime decisions
我正在重写一些现有代码 - 以前,所有答案信息都存储在内存中的字符串数组中。根据数据类型,数据在各个地方进行了转换。下面是我的目标设置的快速模型。本质上你有一些问题——存储在数据库中的答案的结构取决于数据类型。通常我避免处理 void*,并将它们转换为适当的类型——但我找不到更好的解决方案来允许我 运行 通用代码(通过 lambda),或者如果数据类型是特定的众所周知。模板化 类 在这种情况下无济于事,因为所有答案都需要存储在同一个向量中(因为一些算法会根据预定义规则应用于所有答案)。
如有任何建议,我们将不胜感激。
#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;
}
variant
是执行此操作的干净方法。将其存储在 parent.
或者,在 parent 中提供 variant<A,B> GetData()
。现在访问被封装在返回的变体中。 parent 存储数据。
或者,提供 virtual variant<A,B> GetData() = 0
。 child 在相关变体中输入 returns 数据,A
或 B
。
或者,写 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>)
.
调用正确的虚拟接口
在这些虚拟案例中,数据存储在派生类型中。
我正在重写一些现有代码 - 以前,所有答案信息都存储在内存中的字符串数组中。根据数据类型,数据在各个地方进行了转换。下面是我的目标设置的快速模型。本质上你有一些问题——存储在数据库中的答案的结构取决于数据类型。通常我避免处理 void*,并将它们转换为适当的类型——但我找不到更好的解决方案来允许我 运行 通用代码(通过 lambda),或者如果数据类型是特定的众所周知。模板化 类 在这种情况下无济于事,因为所有答案都需要存储在同一个向量中(因为一些算法会根据预定义规则应用于所有答案)。
如有任何建议,我们将不胜感激。
#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;
}
variant
是执行此操作的干净方法。将其存储在 parent.
或者,在 parent 中提供 variant<A,B> GetData()
。现在访问被封装在返回的变体中。 parent 存储数据。
或者,提供 virtual variant<A,B> GetData() = 0
。 child 在相关变体中输入 returns 数据,A
或 B
。
或者,写 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>)
.
在这些虚拟案例中,数据存储在派生类型中。