Return一个成员变量如果存在
Return a member variable if it exist
不知怎的,我觉得类似的问题有几个答案,但是我找不到最终的解决方案problem.So,我提前道歉:
我有许多传入的消息结构:
struct X_1 //Y_2, Z_x, _...
{
IncomingHeader incoming_header;
//.......
};
或传出:
struct A_1 //B_2, C_x, _...
{
OutgoingHeader outgoing_header;
//.......
};
消息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()
函数,它将 return header 成员(incoming_header
或 outgoing_header
)。
有没有办法解决这个问题?
(我使用的是 boost 1.46 而不是 C++11)
由于 C++ 是一种静态类型的语言,因此您必须 return 两者都通过相同的类型。由于您只对头结构的 a
和 b
成员感兴趣,一个明显的解决方案是使用从 [=15 派生的 IncomingHeader
和 OutgoingHeader
=] 然后 return 指向该基数的引用或指针。
struct BaseHeader
{
A a;
B b;
};
struct IncomingHeader : BaseHeader
{
/* ... */
} incoming_header;
struct OutgoingHeader : BaseHeader
{
/* ... */
} outgoing_header;
BaseHeader const&get_header() const
{
if(/* ... */) return incoming_header;
return outgoing_header;
}
好吧,Walter 通过引入通用基类型解决了这个问题。但通常有两种方法可以处理 encoded/marshalled 数据。
- 将网络字节直接映射到数据结构,例如C/C++ POD 类型
- 使用与系统无关的数据表示格式,例如Google Protobuf、XDR、ASN.1 等等(甚至是非二进制的,如 XML、JSON、YAML...)
案例一:POD 类处理
C/C++ POD
其实我唯一不同意Walter的想法的地方就是引入了虚拟基地class。特别是,因为类型不再是 POD 并且无法将其映射 1:1 到网络字节并且需要复制数据。
通常,您示例中的 A
、B
等类型被设计为 PODs。这样就可以非常高效地 marshalling/unmarshalling 无需复制。
假设你有 smth。喜欢:
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 来确保我们处理的是精确长度的字段。不幸的是,标准定义它们是可选的,因此可能在您的目标平台上不可用(实际上很少用于完全成熟的硬件,并且可能是某些嵌入式硬件的情况)。
发送PODs
现在因为这些类型是 POD,我们可以通过简单地通过网络推送它们的字节来简单地发送它们。
所以下面的伪代码是完全OK的:
outgoing_header oh{...};
send(&oh, sizeof(oh));
正在接收PODs
通常你知道如何(从你的协议中你需要多少字节),假设它们都被复制到连续的缓冲区中,你可以获得对该缓冲区的正确视图。假设我们此时不处理 big/little 端序问题。然后网络代码通常会为您接收字节并说明这些字节有多少。
所以在这一点上让我们相信我们现在只能接收 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;
不涉及副本,只是指针转换。
解决您的问题
通常任何二进制协议都有一个共同的 header 发送方和接收方可以依赖。有encoded,携带的是哪个消息,多长,可能是协议版本等
你需要做的是引入一个公共的header,在你的情况下它应该携带字段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 都是 PODs。您在这里需要做的是将您的缓冲区转换为指向 base_header 的指针并获取感兴趣的 a
和 b
:
base_header* pbase_header = reinterpret_cast<base_header*>(&buf[0]);
do_smth(pbase_header->a, pbase_header->b);
案例 2:系统独立数据表示格式
该方法的替代方法是使用 boost::variant
class or if you switch to C++17 std::variant
。如果您不能拥有 PODs 并且拥有某种带有自定义 marshalling/unmarshalling 库的自定义序列化格式,例如Google Protobuf 或类似的...
使用变体你可以定义你的协议,即 messages/headers 可能到达:
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。如果你有兴趣了解为什么需要 variant 或调度是如何工作的,你可以阅读 Dr. Dobbs 中 Andrei Alexandrescu 关于受歧视工会的系列文章:
At some point during decoding, I need a get_header() function which would return the header member(incoming_header or outgoing_header).
您不需要统一签名,所以,这很简单:
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);
}
}
使其通用化
因此您不必为每种消息类型添加重载:
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)
关于具体化 Headers(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);
}
不知怎的,我觉得类似的问题有几个答案,但是我找不到最终的解决方案problem.So,我提前道歉: 我有许多传入的消息结构:
struct X_1 //Y_2, Z_x, _...
{
IncomingHeader incoming_header;
//.......
};
或传出:
struct A_1 //B_2, C_x, _...
{
OutgoingHeader outgoing_header;
//.......
};
消息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()
函数,它将 return header 成员(incoming_header
或 outgoing_header
)。
有没有办法解决这个问题?
(我使用的是 boost 1.46 而不是 C++11)
由于 C++ 是一种静态类型的语言,因此您必须 return 两者都通过相同的类型。由于您只对头结构的 a
和 b
成员感兴趣,一个明显的解决方案是使用从 [=15 派生的 IncomingHeader
和 OutgoingHeader
=] 然后 return 指向该基数的引用或指针。
struct BaseHeader
{
A a;
B b;
};
struct IncomingHeader : BaseHeader
{
/* ... */
} incoming_header;
struct OutgoingHeader : BaseHeader
{
/* ... */
} outgoing_header;
BaseHeader const&get_header() const
{
if(/* ... */) return incoming_header;
return outgoing_header;
}
好吧,Walter 通过引入通用基类型解决了这个问题。但通常有两种方法可以处理 encoded/marshalled 数据。
- 将网络字节直接映射到数据结构,例如C/C++ POD 类型
- 使用与系统无关的数据表示格式,例如Google Protobuf、XDR、ASN.1 等等(甚至是非二进制的,如 XML、JSON、YAML...)
案例一:POD 类处理
C/C++ POD
其实我唯一不同意Walter的想法的地方就是引入了虚拟基地class。特别是,因为类型不再是 POD 并且无法将其映射 1:1 到网络字节并且需要复制数据。
通常,您示例中的 A
、B
等类型被设计为 PODs。这样就可以非常高效地 marshalling/unmarshalling 无需复制。
假设你有 smth。喜欢:
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 来确保我们处理的是精确长度的字段。不幸的是,标准定义它们是可选的,因此可能在您的目标平台上不可用(实际上很少用于完全成熟的硬件,并且可能是某些嵌入式硬件的情况)。
发送PODs
现在因为这些类型是 POD,我们可以通过简单地通过网络推送它们的字节来简单地发送它们。
所以下面的伪代码是完全OK的:
outgoing_header oh{...};
send(&oh, sizeof(oh));
正在接收PODs
通常你知道如何(从你的协议中你需要多少字节),假设它们都被复制到连续的缓冲区中,你可以获得对该缓冲区的正确视图。假设我们此时不处理 big/little 端序问题。然后网络代码通常会为您接收字节并说明这些字节有多少。
所以在这一点上让我们相信我们现在只能接收 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;
不涉及副本,只是指针转换。
解决您的问题
通常任何二进制协议都有一个共同的 header 发送方和接收方可以依赖。有encoded,携带的是哪个消息,多长,可能是协议版本等
你需要做的是引入一个公共的header,在你的情况下它应该携带字段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 都是 PODs。您在这里需要做的是将您的缓冲区转换为指向 base_header 的指针并获取感兴趣的 a
和 b
:
base_header* pbase_header = reinterpret_cast<base_header*>(&buf[0]);
do_smth(pbase_header->a, pbase_header->b);
案例 2:系统独立数据表示格式
该方法的替代方法是使用 boost::variant
class or if you switch to C++17 std::variant
。如果您不能拥有 PODs 并且拥有某种带有自定义 marshalling/unmarshalling 库的自定义序列化格式,例如Google Protobuf 或类似的...
使用变体你可以定义你的协议,即 messages/headers 可能到达:
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。如果你有兴趣了解为什么需要 variant 或调度是如何工作的,你可以阅读 Dr. Dobbs 中 Andrei Alexandrescu 关于受歧视工会的系列文章:
At some point during decoding, I need a get_header() function which would return the header member(incoming_header or outgoing_header).
您不需要统一签名,所以,这很简单:
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);
}
}
使其通用化
因此您不必为每种消息类型添加重载:
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)
关于具体化 Headers(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);
}