Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a move constructor of a tagged union

I implemented a tagged union using a class containing an anonymous union and a tag:

class LogFile
{
  public:
    LogFile(std::ostream& stdStream);
    LogFile(std::ofstream fileStream);
    LogFile(LogFile&& logFile);
    ~LogFile();

    std::ostream& getStream();

  private:
    enum { STD_STREAM, FILE_STREAM } streamType_;
    union
    {
        std::ostream *stdStream_;
        std::ofstream fileStream_;
    };
};

I have trouble implementing the move constructor. In the overloaded "normal" constructors I know which union member to initialize:

LogFile::LogFile(std::ofstream fileStream)
: streamType_(FILE_STREAM), fileStream_(std::move(fileStream))
{
}

But in the move constructor, how do I know which of stdStream_ or fileStream_ I have to initialize. I can't check the value of streamType_ in the initializer list.

like image 248
timakro Avatar asked May 01 '18 10:05

timakro


1 Answers

Unless you do it for practice, replace your tagged union with a std::variant. It's a lot more safe.


Instead of calling a constructor in member initializer list, you can conditionally call it later using placement-new.

Usually, if you don't specify a constructor in member initializer list, a default one is called. But for members of union {...};, no constructor is called at all.

LogFile(LogFile&& other) : streamType_(other.streamType_)
{
    switch (streamType_)
    {
      case FILE_STREAM:
        new (&fileStream_) std::ofstream(std::move(other.fileStream_)); // <--
        break;
      case STD_STREAM:
        stdStream_ = other.stdStream_;
        other.stdStream_ = 0;
        break;
    }
}

Note that you have to manually call the destructor in ~LogFile(), and you need a custom move assignment too. That's why std::variant is better.

like image 151
HolyBlackCat Avatar answered Oct 12 '22 16:10

HolyBlackCat