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_headeroutgoing_header)。 有没有办法解决这个问题? (我使用的是 boost 1.46 而不是 C++11)

由于 C++ 是一种静态类型的语言,因此您必须 return 两者都通过相同的类型。由于您只对头结构的 ab 成员感兴趣,一个明显的解决方案是使用从 [=15 派生的 IncomingHeaderOutgoingHeader =] 然后 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 到网络字节并且需要复制数据。

通常,您示例中的 AB 等类型被设计为 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,在你的情况下它应该携带字段ab

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 的指针并获取感兴趣的 ab

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).

您不需要统一签名,所以,这很简单:

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)

关于具体化 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);
}

再看Live On Coliru