返回成员变量(如果存在)

时间:2017-11-13 10:43:26

标签: c++ boost sfinae

不知怎的,我觉得这个问题有几个答案,但我无法找到问题的最终解决方案。所以,我提前道歉: 我有许多消息结构要么传入:

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

3 个答案:

答案 0 :(得分:3)

由于C ++是静态类型语言,因此必须通过相同类型返回。由于您只对标题结构的ab成员感兴趣,因此一个明显的解决方案是使用IncomingHeaderOutgoingHeader来自{ {1}}然后返回一个引用或指向该基数的指针。

BaseHeader

答案 1 :(得分:3)

好吧,沃尔特通过引入一个共同的基本类型来解决这个想法。但通常有两种方法可以处理编码/编组数据。

  • 将网络字节直接映射到数据结构,例如C / C ++ POD类型
  • 使用系统独立的数据表示格式,例如谷歌Protobuf,XDR,ASN.1等等(甚至非二进制的,如XML,JSON,YAML ......)

案例1:POD之类的处理

C / C ++ POD

实际上,我唯一不同意沃尔特想法的部分是引入虚拟基类。特别是,因为类型不再是POD,并且无法将其1:1映射到网络字节,并且需要复制数据。

通常,您示例中的AB等类型设计为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

现在因为这些类型是POD,我们只需通过网络推送它们就可以发送它们。

所以下面的伪代码完全没问题:

outgoing_header oh{...};
send(&oh, sizeof(oh));

接收POD

通常你知道(根据你的协议你需要多少字节),因为它们都被复制到连续的缓冲区中,你可以获得对该缓冲区的正确视图。让我们说我们当时没有处理大/小端问题。然后网络代码通常会为您接收字节,并说明这些字节数。

所以在这一点上,让我们依赖我们现在只能接收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;

没有涉及副本,只是一个指针。

解决您的问题

通常任何二进制协议都有一个共同的头发送方和接收方可以依赖。有编码,正在传输哪条消息,它有多长,可能是协议版本等。

您需要做的是引入一个公共标题,在您的情况下,它应该包含字段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都是POD。你需要做的是将缓冲区转换为指向base_header的指针并抓住感兴趣的ab

base_header* pbase_header = reinterpret_cast<base_header*>(&buf[0]);
do_smth(pbase_header->a, pbase_header->b);

案例2:系统无关的数据表示格式

该方法的替代方法是使用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