在现代C+中具有可选成员的结构+; 我们继承了旧代码,我们正在转换为现代C++,以获得更好的类型安全、抽象和其他优点。我们有许多具有许多可选成员的结构,例如: struct Location { int area; QPoint coarse_position; int layer; QVector3D fine_position; QQuaternion rotation; };
重要的一点是,所有成员都是可选的。在任何给定的位置实例中至少会出现一个,但不一定全部。可能会有更多的组合,这显然比最初的设计人员发现的使用单独的结构来表达更方便 结构以这种方式反序列化(伪代码): 在旧代码中,这些标志永久存储在结构中,每个结构成员的getter函数在运行时测试这些标志,以确保请求的成员存在于给定的位置实例中 我们不喜欢这种运行时检查系统。请求一个不存在的成员是一个严重的逻辑错误,我们希望在编译时发现这个错误。这应该是可能的,因为无论何时读取位置,都知道应该存在哪个成员变量组合 首先,我们考虑使用std::optional:在现代C+中具有可选成员的结构+; 我们继承了旧代码,我们正在转换为现代C++,以获得更好的类型安全、抽象和其他优点。我们有许多具有许多可选成员的结构,例如: struct Location { int area; QPoint coarse_position; int layer; QVector3D fine_position; QQuaternion rotation; };,c++,c++11,design-patterns,C++,C++11,Design Patterns,重要的一点是,所有成员都是可选的。在任何给定的位置实例中至少会出现一个,但不一定全部。可能会有更多的组合,这显然比最初的设计人员发现的使用单独的结构来表达更方便 结构以这种方式反序列化(伪代码): 在旧代码中,这些标志永久存储在结构中,每个结构成员的getter函数在运行时测试这些标志,以确保请求的成员存在于给定的位置实例中 我们不喜欢这种运行时检查系统。请求一个不存在的成员是一个严重的逻辑错误,我们希望在编译时发现这个错误。这应该是可能的,因为无论何时读取位置,都知道应该存在哪个成员变量组合
struct Location {
std::optional<int> area;
std::optional<QPoint> coarse_location;
// etc.
};
结构位置{
std:可选区域;
std::可选的粗略位置;
//等等。
};
此解决方案使设计缺陷现代化,而不是修复它
我们考虑使用std::variant,如下所示:
struct Location {
struct Has_Area_and_Coarse {
int area;
QPoint coarse_location;
};
struct Has_Area_and_Coarse_and_Fine {
int area;
QPoint coarse_location;
QVector3D fine_location;
};
// etc.
std::variant<Has_Area_and_Coarse,
Has_Area_and_Coarse_and_Fine /*, etc.*/> data;
};
结构位置{
结构具有面积和粗糙度{
内部区域;
QPoint粗化_位置;
};
结构有_区域_和_粗_和_细{
内部区域;
QPoint粗化_位置;
QVector3D精细定位;
};
//等等。
std::变量数据;
};
这种解决方案使得非法状态无法表示,但当成员变量的组合可能超过几个时,无法很好地扩展。此外,我们不希望通过指定Has\u Area\u和\u rough来访问,而是通过更接近loc.fine\u位置的方式来访问
这个问题有没有我们没有考虑过的标准解决方案?关于mixin呢
struct QPoint {};
struct QVector3D {};
struct Area {
int area;
};
struct CoarsePosition {
QPoint coarse_position;
};
struct FinePosition {
QVector3D fine_position;
};
template <class ...Bases>
struct Location : Bases... {
};
Location<Area, CoarsePosition> l1;
Location<Area, FinePosition> l2;
structqpoint{};
结构QVector3D{};
结构区{
内部区域;
};
结构粗定位{
QPoint粗齌位置;
};
结构精细定位{
QVector3D精细位置;
};
模板
结构位置:基础。。。{
};
位置l1;
位置l2;
首先我要说的是,我偶尔也想对一个类进行“可选化”,其中所有成员都是可选的。我在想,如果没有使用类似Antony Polukhin的代码进行适当的元编程,这可能是可能的
尽管如此。。。您可以拥有具有任意类型值的部分类型安全属性映射:
class Location {
enum class Attribute { area, coarse_position, fine_position, layer };
std::unoredered_map<Attribute, std::any> attributes;
}
属性的使用可能涉及检查相关属性是否存在的自由函数
在实践中,顺便说一句,std::vector
可能比std::unordered_map
搜索得更快
警告:此解决方案没有提供您所需的类型安全性。您可以拥有使位图在编译时进行检查的结构版本。我假设对于一段特定的代码,您可以对当前的内容进行假设。在该代码中,您可以使用编译时位图获取版本。为了成功地将运行时位映射版本转换为编译时位映射,将验证位映射
#include <stdexcept>
struct foo
{
int a;
float b;
char c;
};
struct rt_foo : foo
{
unsigned valid;
};
template <unsigned valid>
struct ct_foo : foo
{
// cannnot default construct
ct_foo () = delete;
// cannot copy from version withouth validity flags
ct_foo (foo const &) = delete;
ct_foo & operator = (foo const &) = delete;
// copying from self is ok
ct_foo (ct_foo const &) = default;
ct_foo & operator = (ct_foo const &) = default;
// converting constructor and assignement verify the flags
ct_foo (rt_foo const & rtf) :
foo (check (rtf))
{
}
ct_foo & operator = (rt_foo const & rtf)
{
*static_cast <foo*> (this) = check (rtf);
return *this;
}
// using a member that is not initialize will be a compile time error at when
// instantiated, which will occur at the time of use
auto & get_a () { static_assert (valid & 1); return a; }
auto & get_b () { static_assert (valid & 2); return a; }
auto & get_c () { static_assert (valid & 3); return a; }
// helper to validate the runtime conversion
static foo & check (rt_foo const & rtf)
{
if ((valid & rtf.valid) != 0)
throw std::logic_error ("bad programmer!");
}
};
#包括
结构foo
{
INTA;
浮球b;
字符c;
};
结构rt_foo:foo
{
无符号有效;
};
模板
结构ct_foo:foo
{
//不能使用默认构造
ct_foo()=删除;
//无法从没有有效性标志的版本复制
ct_foo(foo const&)=删除;
ct_foo&运算符=(foo const&)=删除;
//自我复制是可以的
ct_foo(ct_foo const&)=默认值;
ct\u foo&运算符=(ct\u foo const&)=默认值;
//转换构造函数和赋值验证标志
ct_foo(rt_foo const&rtf):
foo(支票(rtf))
{
}
ct_-foo和运算符=(rt_-foo常量和rtf)
{
*静态(本)=检查(rtf);
归还*这个;
}
//当使用未初始化的成员时,将出现编译时错误
//实例化,将在使用时发生
auto&get_a(){static_assert(valid&1);返回a;}
auto&get_b(){static_assert(valid&2);返回a;}
auto&get_c(){static_assert(valid&3);返回a;}
//帮助程序来验证运行时转换
静态foo&check(rt\u foo const&rtf)
{
如果((有效&rtf.valid)!=0)
抛出std::logic_错误(“糟糕的程序员!”);
}
};
如果您总是知道在读取或构造时会出现哪些字段,那么将有效位设为模板参数并使用static\u assert检查将起作用
#include <stdexcept>
#include <iostream>
struct stream {
template <typename value> value read ();
template <typename value> void read (value &);
};
template <unsigned valid>
struct foo
{
int a;
float b;
char c;
auto & get_a () { static_assert (valid & 1); return a; }
auto & get_b () { static_assert (valid & 2); return b; }
auto & get_c () { static_assert (valid & 4); return c; }
};
template <unsigned valid>
foo <valid> read_foo (stream & Stream)
{
if (Stream.read <unsigned> () != valid)
throw std::runtime_error ("unexpected input");
foo <valid> Foo;
if (valid & 1) Stream.read (Foo.a);
if (valid & 2) Stream.read (Foo.b);
if (valid & 4) Stream.read (Foo.c);
}
void do_something (stream & Stream)
{
auto Foo = read_foo <3> (Stream);
std::cout << Foo.get_a () << ", " << Foo.get_b () << "\n";
// don't touch c cause it will fail here
// Foo.get_c ();
}
我觉得这个代码的设计有问题。类字段本身不能是可选的,即使所有字段都以某种方式包装,那么使用此类结构的所有代码都将被可选性检查弄得乱七八糟。不太清楚为什么您会将其视为设计缺陷。您能给出由于这种设计而产生的问题的具体示例吗?如果您想防止请求未初始化的成员,最简单的解决方案就是使用一个初始化所有成员的构造函数。有没有理由不使用?如果您想要更现代的方法,元组还可以与std::get结合使用,以访问在运行时推出的成员。如果您不想“请求不存在的成员”,您应该为每个可能的字段组合定义单独的类(如果有意义,可以使用类层次结构),在基类((纯)虚拟)中定义所需的操作,并在派生类中实现它们。那么你就不再需要任何运行时检查了。但是虚拟函数调用可能比simp慢一点
#include <stdexcept>
struct foo
{
int a;
float b;
char c;
};
struct rt_foo : foo
{
unsigned valid;
};
template <unsigned valid>
struct ct_foo : foo
{
// cannnot default construct
ct_foo () = delete;
// cannot copy from version withouth validity flags
ct_foo (foo const &) = delete;
ct_foo & operator = (foo const &) = delete;
// copying from self is ok
ct_foo (ct_foo const &) = default;
ct_foo & operator = (ct_foo const &) = default;
// converting constructor and assignement verify the flags
ct_foo (rt_foo const & rtf) :
foo (check (rtf))
{
}
ct_foo & operator = (rt_foo const & rtf)
{
*static_cast <foo*> (this) = check (rtf);
return *this;
}
// using a member that is not initialize will be a compile time error at when
// instantiated, which will occur at the time of use
auto & get_a () { static_assert (valid & 1); return a; }
auto & get_b () { static_assert (valid & 2); return a; }
auto & get_c () { static_assert (valid & 3); return a; }
// helper to validate the runtime conversion
static foo & check (rt_foo const & rtf)
{
if ((valid & rtf.valid) != 0)
throw std::logic_error ("bad programmer!");
}
};
#include <stdexcept>
#include <iostream>
struct stream {
template <typename value> value read ();
template <typename value> void read (value &);
};
template <unsigned valid>
struct foo
{
int a;
float b;
char c;
auto & get_a () { static_assert (valid & 1); return a; }
auto & get_b () { static_assert (valid & 2); return b; }
auto & get_c () { static_assert (valid & 4); return c; }
};
template <unsigned valid>
foo <valid> read_foo (stream & Stream)
{
if (Stream.read <unsigned> () != valid)
throw std::runtime_error ("unexpected input");
foo <valid> Foo;
if (valid & 1) Stream.read (Foo.a);
if (valid & 2) Stream.read (Foo.b);
if (valid & 4) Stream.read (Foo.c);
}
void do_something (stream & Stream)
{
auto Foo = read_foo <3> (Stream);
std::cout << Foo.get_a () << ", " << Foo.get_b () << "\n";
// don't touch c cause it will fail here
// Foo.get_c ();
}
template <unsigned valid>
void print_foo (std::ostream & os, foo <valid> const & Foo)
{
if constexpr (valid & 1)
os << "a = " << Foo.get_a () << "\n";
if constexpr (valid & 2)
os << "b = " << Foo.get_b () << "\n";
if constexpr (valid & 4)
os << "c = " << Foo.get_c () << "\n";
}