Constructor and copy-constructor for class containing union with non-trivial members

(https://stackoverflow.com/questions/30492927/constructor-and-copy-constructor-for-class-containing-union-with-non-trivial-mem)
I am trying to implement a custom variant type which uses a union to store data of various different types. In the field type_id I plan to store which type the data stored in the union is of. The union contains non-trivial members. Here is my current implementation:

struct MyVariant {
  enum { t_invalid, t_string, t_int, t_double, t_ptr, t_dictionary } type_id;
  union {
    int                             as_int;
    double                          as_double;
    std::string                     as_string;
    std::unique_ptr<int>            as_ptr;
    std::map<int, double>           as_dictionary;
  };
};

I try to create an instance of MyVariant like follows:

MyVariant v;
I get the error message: Call to implicitly-deleted default constructor of MyVariant. So, I tried to implement the constructor manually like follows:

MyVariant() : type_id{t_int}, as_int{0} {}
That gives me a similar error message: Attempt to use a deleted function. Next, I tried to implement the following constructor:

MyVariant(int value) : type_id{t_int}, as_int{value} {}
and construct my instance like follows:

MyVariant v{123};
=> same error message: Attempt to use a deleted function.

I’ve also started to implement a copy constructor, it looks like follows. However, of course this doesn’t help with the compiler errors.

MyVariant::MyVariant(const MyVariant& other)
{
    type_id = other.type_id;
    switch (type_id) {
        case t_invalid:
            break;
        case t_string:
            new (&as_string) std::string();
            as_string = other.as_string;
            break;
        case t_int:
            as_int = other.as_int;
            break;
        case t_double:
            as_double = other.as_double;
            break;
        case t_ptr:
            new (&as_ptr) std::unique_ptr<int>(nullptr);
            as_ptr = std::make_unique<int>(*other.as_ptr);
            break;
        case t_dictionary:
            new (&as_dictionary) std::map<int, double>();
            // TODO: copy values from other
            break;
    }
}

I am using Xcode and Apple LLVM 6.1 as compiler.

The main question is: Why do I get the compiler errors which I’m getting and how do I have to modify my code to make it compile?

The additional question is: Am I on the right way with my implementations for the constructor and copy constructor?

c++ c++11 constructor c++14 unions
shareimprove this question
edited May 27 ‘15 at 21:31

T.C.
99.9k13198304
asked May 27 ‘15 at 20:53

j00hi
1,78632549
That default constructor is OK. You need to implement a destructor. – T.C. May 27 ‘15 at 21:31
Did you mean to use braces instead of parens where your one instance is allocated? – donjuedo May 27 ‘15 at 21:43
@donjuedo Yes, i meant to use braces because I’d like to stick to the uniform C++11 initialization syntax. Is there anything wrong with them? – j00hi May 29 ‘15 at 8:38
@j00hi No, I don’t think there is anything wrong with braces, not directly anyway. Having run into a mysterious (subtle) type casting problem recently, I am wondering if there is more going on under the hood to get that 123 to become an object. My suggestion would be to try parens anyway, to see if directly passing an int to a constructor you explicitly choose would sidestep the error. – donjuedo May 29 ‘15 at 11:43
add a comment
1 Answer
active oldest votes
up vote
7
down vote
accepted
Your union has data members of type string, unique_ptr and map, all of which have non-trivial default/copy/move constructors, copy/move assignment operators and destructors. Hence all of these are implicitly deleted for your union.

§9.5/2 [class.union]

  • ... [ Note: If any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-provided or it will be implicitly deleted (8.4.3) for the union. —end note ]

So you must manually implement these for your union. At a minimum, for you to be able to create an instance of MyVariant, the class needs to be constructible and destructible. So you need

MyVariant() : type_id{t_int}, as_int{0} {}
~MyVariant()
{
  switch(type_id)
  {
      case t_int:
      case t_double:
        // trivially destructible, no need to do anything
        break;
      case t_string:
        as_string.~basic_string();
        break;
      case t_ptr:
        as_ptr.~unique_ptr();
        break;
      case t_dictionary:
        as_dictionary.~map();
        break;
      case t_invalid:
        // do nothing
        break;
      default:
        throw std::runtime_error("unknown type");
  }
}

Your copy constructor implementation looks valid, but what I’d do differently is instead of first default constructing the member, and then copying from the source object, just copy construct in the placement new call itself.

MyVariant(const MyVariant& other)
{
  type_id = other.type_id;
  switch (type_id) {
      case t_invalid:
          break;
      case t_string:
          new (&as_string) auto(other.as_string);
          break;
      case t_int:
          as_int = other.as_int;
          break;
      case t_double:
          as_double = other.as_double;
          break;
      case t_ptr:
          new (&as_ptr) auto(std::make_unique<int>(*other.as_ptr));
          break;
      case t_dictionary:
          new (&as_dictionary) auto(other.as_dictionary);
          break;
  }

Live demo

Note that if the unique_ptr member is active, and is storing a pointer to some derived class instance via a base class pointer, then your copy constructor implementation will only copy the base class part.

Finally, unless you’re doing this as a learning exercise, I’d strongly urge you to use Boost.Variant instead of rolling your own.

shareimprove this answer
edited May 29 ‘15 at 12:28
answered May 27 ‘15 at 21:38

Praetorian
84.8k10175253
3
Tidbit: new (&as_dictionary) auto(other.as_dictionary) and so on can get rid of some of the redundancy, and is more refactoring friendly. – Luc Danton May 28 ‘15 at 8:41
Boost.Variant is quite outdated for the current state of c++. – Orient May 29 ‘15 at 2:17
@Orient Why do you think, Boost.Varant is outdated? And what would be a good, modern alternative to it? – j00hi May 29 ‘15 at 8:40
@j00hi apply_visitor (integral part of Boost.Variant) not allow us to distinct ref-qualified visitor’s operator () properly. – Orient May 29 ‘15 at 9:53
1
@abraham_hilbert It’s placement new syntax, you’re passing the address where you want the object to be constructed. – Praetorian Dec 18 ‘16 at 0:29
show 4 more comments

#include <iostream>
#include <memory>
#include <map>
#include <vector>
#include <string>

struct MyVariant {
  enum { t_invalid, t_string, t_int, t_double, t_ptr, t_dictionary } type_id;
  union {
    int                             as_int;
    double                          as_double;
    std::string                     as_string;
    std::unique_ptr<int>            as_ptr;
    std::map<int, double>           as_dictionary;
  };

    MyVariant() : type_id{t_int}, as_int{0} {}
    ~MyVariant()
    {
      switch(type_id)
      {
          case t_int:
          case t_double:
            // trivially destructible, no need to do anything
            break;
          case t_string:
            as_string.~basic_string();
            break;
          case t_ptr:
            as_ptr.~unique_ptr();
            break;
          case t_dictionary:
            as_dictionary.~map();
            break;
          case t_invalid:
            // do nothing
            break;
          default:
            throw std::runtime_error("unknown type");
      }
    }

  MyVariant(const MyVariant& other)
  {
    type_id = other.type_id;
    switch (type_id) {
        case t_invalid:
            break;
        case t_string:
            new (&as_string) auto(other.as_string);
            break;
        case t_int:
            as_int = other.as_int;
            break;
        case t_double:
            as_double = other.as_double;
            break;
        case t_ptr:
            new (&as_ptr) auto(std::make_unique<int>(*other.as_ptr));
            break;
        case t_dictionary:
            new (&as_dictionary) auto(other.as_dictionary);
            break;
    }
  }
};

int main()
{
    MyVariant v;
}

猜你喜欢

转载自blog.csdn.net/commshare/article/details/81515901