简体   繁体   中英

C++ implementation of protobuf `oneof` feature for non-protobuf class

The protobuf oneof feature is great. But it only can be used when the fields in oneof are either a primitive type or a protobuf message. What if I have two classes A and B , which are defined by C++ code instead of protobuf messages, and I want to implement a class AorB which is like:

message AorB {
    oneof oneof_name {
        A a = 1;
        B b = 2;
    }
}

I tried to read the generated C++ code of a oneof field to see how that is implemented. But it's pretty complicated. Is there any concise way to implement this? Or any template which I can use directly?

Depending on which version of C++ you're able to use, your options are std::variant , roll your own with variadic templates, or roll your own with union . std::variant was added to the language in C++17 and will definitely be the easiest to manage. The variadic template version is tricky.

union works to the beginning of the language and would look something like.

struct MyAorB {
  union {
    A a;
    B b;
  };
  ~MyAorB() { destruct(); }
  MyAorB& operator=(const MyAorB&) = delete;
  MyAorB& operator=(MyAorB&&) = delete;
  MyAorB(const MyAorB&) = delete;
  MyAorB(const MyAorB&&) = delete;
  enum { HOLDS_NONE, HOLDS_A, HOLDS_B } which_one = HOLDS_NONE;
  A& get_A() { assert(which_one == HOLDS_A); return a; }
  B& get_B() { assert(which_one == HOLDS_B); return b; }
  void set_A(A new_a) { which_one = HOLDS_A; destruct(); a = std::move(new_a); }
  void set_B(B new_b) { which_one = HOLDS_B; destruct(); b = std::move(new_b); }
  void destruct() {
    switch (which_one) {
      case HOLDS_A: a.~A(); break;
      case HOLDS_B: b.~B(); break;
      default: break;
    }
  }
};

at a baseline that kinda maybe works. There's a pile of details to get it right, though. The basics of it are that a union puts the values in overlapping memory, only one is valid at a time, and it's undefined behavior to access the wrong one. You also need to manually destruct before re-assigning the held value.

I probably missed a detail somewhere in there. I'd rather leave it to std::variant but if you need to write your own discriminated union, it would start something like the code above.

More details on variant here: https://en.cppreference.com/w/cpp/utility/variant

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM