用例:根据“源”中的枚举值获取属性并在结构
Attributes
中设置值。如果无法检索值,则返回 ErrorCode
:
enum class ErrorCode
{
OK,
FAILURE
};
enum AttributeType
{
TYPE1,
TYPE2,
TYPE3
};
struct Attributes
{
uint16_t attribute1;
uint8_t attribute2;
bool attribute3;
};
建议的解决方案:使用工厂,其中基类定义接口方法
get
,派生类(每个属性类型)实现该方法。整个 Attributes
对象需要传递给该方法,并且在一个实现中仅设置结构体的一个成员(基于属性类型)。
class AttributeRetriever
{
public:
virtual ErrorCode get(Attributes& attributes) = 0;
};
class Attributetype1 : public AttributeRetriever
{
public:
ErrorCode get(Attributes& attributes) override
{
attributes.attribute1 = 60;
return ErrorCode::OK;
}
};
std::unique_ptr<AttributeRetriever> factory(AttributeType at)
{
if (at == TYPE1) return std::make_unique<Attributetype1>();
...
}
我的解决方案:使用模板。我们没有将
Attributes
传递给方法,而是从中返回所需的值。由于我们需要返回结果值或错误,因此我创建了一个类Result
。我的解决方案创建一个模板结构 TypeMap
,以枚举值作为参数,其中 type
等于 Attributes
中的属性类型(基于枚举值):
template <typename ValueType>
class Result
{
public:
static_assert(std::is_copy_constructible<ValueType>::value && std::is_copy_assignable<ValueType>::value, "ValueType must be both copy-constructible and copy-assignable");
static_assert(std::is_move_constructible<ValueType>::value && std::is_move_assignable<ValueType>::value, "ValueType must be both move-constructible and move-assignable");
explicit Result(ErrorCode e) noexcept : m_value{}, m_error{e}, m_has_error{true} {}
explicit Result(ValueType t) noexcept : m_value{t}, m_error{ErrorCode::OK}, m_has_error{false} {}
[[nodiscard]] bool has_value() const noexcept { return !m_has_error; }
[[nodiscard]] bool has_error() const noexcept { return m_has_error; }
[[nodiscard]] ValueType value() const noexcept { return m_value; }
[[nodiscard]] ErrorCode error() const noexcept { return m_error; }
private:
const ValueType m_value;
const ErrorCode m_error;
const bool m_has_error;
};
template<AttributeType> struct TypeMap;
template<>
struct TypeMap<AttributeType::TYPE1>
{
using type = decltype(Attributes::attribute1);
};
template<>
struct TypeMap<AttributeType::TYPE2>
{
using type = decltype(Attributes::attribute2);
};
template<>
struct TypeMap<AttributeType::TYPE3>
{
using type = decltype(Attributes::attribute3);
};
class AttributeRetriever
{
public:
template<AttributeType AT>
Result<typename TypeMap<AT>::type> get()
{
using R = Result<typename TypeMap<AT>::type>;
if constexpr(AT == AttributeType::TYPE1)
{
return R(60);
}
else if (AT == AttributeType::TYPE2)
{
return R(3);
}
else if (AT == AttributeType::TYPE3)
{
return R(false);
}
}
};
int main()
{
AttributeRetriever ar;
std::cout << ar.get<AttributeType::TYPE1>().value() << '\n';
std::cout << static_cast<uint32_t>(ar.get<AttributeType::TYPE2>().value()) << '\n';
std::cout << ar.get<AttributeType::TYPE3>().value() << '\n';
}
我的解决方案有效。但是
TypeMap
需要针对每个属性类型和结构成员进行专门化。有没有更优雅的解决方案TypeMap
?
一种选择是将类型映射缩短为:
template<AE i>
using TypeMap = typename std::tuple_element<i, std::tuple<
decltype(A::a),
decltype(A::b)
>>::type;
C++ 不支持这种反射:您无法轻松访问声明结构体字段的顺序。除了构造函数中的初始化顺序或手动分析偏移量之外,以下结构
struct Attributes
{
uint8_t attribute2;
uint16_t attribute1;
bool attribute3;
};
将相当于您的
Attributes
。
现有的迂回解决方案如“magic get”,但通常有两种方法可以做类似的事情。
a) 指向成员的指针。您可以将它们用作模板参数:
template<auto m> struct field_helper;
template<typename T, typename FieldType, FieldType T::*memberPtr>
struct field_helper<memberPtr> {
static FieldType get(const T* ptr){ return ptr->*memberPtr; }
};
在这种情况下,您的枚举完全消失:它与字段名称合并,删除重复项。如果您不需要代码来迭代每个结构体字段,我建议尝试走这条路线。
b) 宏。您可以使用为每个类成员创建结构专业化的宏来声明字段:
#define DECLARE_FIELD(IDX, Type, name) Type name; \
template<typename C> struct field_helper<IDX, C> { \
static Type get(MainType* ptr) { return ptr->name; } \
}
struct Atrributes {
//need typedef since you can't get type name within the type
typedef Attributes MainType;
//need extra template parameter to prevent full specialization
template<int idx, typename = void> struct field_helper;
DECLARE_FIELD(0, uint16_t, attribute1);
//...
};
有了这个,所有字段都被明确编号:您可以对它们进行计数,迭代它们,通过索引访问它们的类型等。(您可以通过更多宏观工作自动编号,但这超出了您的问题的范围。)