不知怎的,我觉得这个问题有几个答案,但我无法找到问题的最终解决方案。所以,我提前道歉: 我有许多消息结构要么传入:
struct X_1 //Y_2, Z_x, _...
{
IncomingHeader incoming_header;
//.......
};
或传出:
struct A_1 //B_2, C_x, _...
{
OutgoingHeader outgoing_header;
//.......
};
邮件标题只有两种类型:
struct IncomingHeader
{
A a;
B b;
};
struct OutgoingHeader
{
A a;
B b;
char c[SIZE};
};
//If it helps, eventually I am only interested in a and b in header structs.
在解码过程中的某个时刻,我需要一个get_header()
函数来返回标题成员(incoming_header
或outgoing_header
)。
有没有办法解决这个问题?
(我使用的是boost 1.46而不是C ++ 11)
答案 0 :(得分:3)
由于C ++是静态类型语言,因此必须通过相同类型返回。由于您只对标题结构的a
和b
成员感兴趣,因此一个明显的解决方案是使用IncomingHeader
和OutgoingHeader
来自{ {1}}然后返回一个引用或指向该基数的指针。
BaseHeader
答案 1 :(得分:3)
好吧,沃尔特通过引入一个共同的基本类型来解决这个想法。但通常有两种方法可以处理编码/编组数据。
实际上,我唯一不同意沃尔特想法的部分是引入虚拟基类。特别是,因为类型不再是POD,并且无法将其1:1映射到网络字节,并且需要复制数据。
通常,您示例中的A
,B
等类型设计为POD。这样就可以非常有效地编组/解组它们而无需复制。
让我们说你有事。像:
struct incoming_header
{
std::int32_t a;
std::int64_t b;
};
struct outgoing_header
{
std::int32_t a;
std::int64_t b;
char c[SIZE};
};
这里我们使用C++ standard's guaranteed length integers来确保我们处理确切的字段长度。不幸的是,标准定义它们是可选的,因此可能在目标平台上不可用(实际上很少用于完全成熟的硬件,并且可能是某些嵌入式硬件的情况)。
现在因为这些类型是POD,我们只需通过网络推送它们就可以发送它们。
所以下面的伪代码完全没问题:
outgoing_header oh{...};
send(&oh, sizeof(oh));
通常你知道(根据你的协议你需要多少字节),因为它们都被复制到连续的缓冲区中,你可以获得对该缓冲区的正确视图。让我们说我们当时没有处理大/小端问题。然后网络代码通常会为您接收字节,并说明这些字节数。
所以在这一点上,让我们依赖我们现在只能接收outgoing_header
而且我们的缓冲区足够大以包含整个消息长度。
然后代码通常类似于:
constexpr static size_t max_size = ...;
char buf[max_size]{};
size_t got_bytes = receive(&buf, max_size);
// now we need to interpret these bytes as outgoing_header
outgoing_header* pheader = reinterpret_cast<outgoing_header*>(&buf[0]);
// now access the header items
pheader->a;
pheader->b;
没有涉及副本,只是一个指针。
通常任何二进制协议都有一个共同的头发送方和接收方可以依赖。有编码,正在传输哪条消息,它有多长,可能是协议版本等。
您需要做的是引入一个公共标题,在您的情况下,它应该包含字段a
和b
。
struct base_header
{
std::int32_t a;
std::int64_t b;
};
// Note! Using derivation will render the type as non-POD, thus aggregation
struct incoming_header
{
base_header base;
};
struct outgoing_header
{
base_header base;
char c[SIZE};
};
现在incoming_header和outgoing_header都是POD。你需要做的是将缓冲区转换为指向base_header的指针并抓住感兴趣的a
和b
:
base_header* pbase_header = reinterpret_cast<base_header*>(&buf[0]);
do_smth(pbase_header->a, pbase_header->b);
该方法的替代方法是使用boost::variant
类或切换到C++17 std::variant
。如果您不能拥有POD并具有某种自定义序列化格式,并使用自定义编组/解组lib,例如Google Protobuf或类似的......
使用变体,您只需定义您的协议,即可能到达的消息/标题:
typedef boost::variant<boost::none, IncomingHeader, OutgoingHeader> message_header;
message_header get_header(char* bytes, size_t size)
{
// dispatch bytes and put the message to variant:
// let's say we get OutgoingHeader
OutgoingHeader h{/* init from bytes here */};
return h; // variant has implicit ctor to accept OutgoingHeader object
}
现在,您可以使用手工制作的visitor类型来获得所需的值:
struct my_header_visitor
{
typedef void result_type;
explicit my_header_visitor(some_context& ctx)
: ctx_{ctx}
{}
template<class T>
result_type operator()(T const&)
{
// throw whatever error, due to unexpected dispatched type
}
result_type operator()(OutgoingHeader const& h)
{
// handle OutgoingHeader
ctx_.do_smth_with_outgoing_header(h);
}
result_type operator()(IncomingHeader const& h)
{
// handle IncomingHeader
ctx_.do_smth_with_incoming_header(h);
}
private:
some_context& ctx_;
};
my_header_visitor v{/* pass context here */};
message_header h {/* some init code here */};
boost::apply_visitor(v, h);
P.S。如果您有兴趣了解为什么需要变体或调度如何工作,您可以阅读Andrei Alexandrescu关于Dobbs博士中歧视联盟的系列文章:
答案 2 :(得分:3)
在解码过程中的某个时刻,我需要一个get_header()函数来返回头部成员(incoming_header或outgoing_header)。
您不需要统一签名,因此,这很简单:
<强> Live On Coliru 强>
IncomingHeader const& get_header(X_1 const& msg) { return msg.incoming_header; }
OutgoingHeader const& get_header(A_1 const& msg) { return msg.outgoing_header; }
使用它:
int main() {
X_1 x;
A_1 a;
// in your decode function:
{
IncomingHeader const& h = get_header(x);
}
{
OutgoingHeader const& h = get_header(a);
}
}
因此您不必为每种消息类型添加重载:
<强> Live On Coliru 强>
template <typename T> auto get_header(T&& msg) -> decltype((msg.incoming_header)) { return msg.incoming_header; }
template <typename T> auto get_header(T&& msg) -> decltype((msg.outgoing_header)) { return msg.outgoing_header; }
您可以将其用于任何声明的类型:
struct X_1 { IncomingHeader incoming_header; };
struct Y_2 { IncomingHeader incoming_header; };
struct Z_x { IncomingHeader incoming_header; };
//or Outgoing :
struct A_1 { OutgoingHeader outgoing_header; };
struct B_2 { OutgoingHeader outgoing_header; };
struct C_x { OutgoingHeader outgoing_header; };
template <typename T>
void decode(T&& msg) {
auto&& header = get_header(msg);
std::cout << typeid(T).name() << " has " << typeid(header).name() << "\n";
}
int main() {
X_1 x;
A_1 a;
decode(x);
decode(a);
decode(Y_2{});
decode(Z_x{});
decode(B_2{});
decode(C_x{});
}
打印
X_1 has IncomingHeader
A_1 has OutgoingHeader
Y_2 has IncomingHeader
Z_x has IncomingHeader
B_2 has OutgoingHeader
C_x has OutgoingHeader
事实上,您可以使用如下的时髦消息类型:
struct Funky { std::map<std::string, std::string> outgoing_header; };
它会打印
Funky has std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >
(全部见 Live On Coliru )
A&
,B&
)您可以拥有更简单的界面:
template <typename T> auto get_header(T&& msg) -> decltype((msg.incoming_header)) { return msg.incoming_header; }
template <typename T> auto get_header(T&& msg) -> decltype((msg.outgoing_header)) { return msg.outgoing_header; }
struct A {};
struct B {};
template <typename T> A const& getHeaderA(T const& msg) { return get_header(msg).a; }
template <typename T> B const& getHeaderB(T const& msg) { return get_header(msg).b; }
这将删除类型差异:
template <typename T>
void decode(T&& msg) {
A const& headerA = getHeaderA(msg);
B const& headerB = getHeaderB(msg);
}
再次查看 Live On Coliru