C++11 类联合移动构造函数 class

C++11 move constructor for union-like class

有没有更好的方法来为类联合 class 构建移动构造函数?如果我在下面的代码中有一个像 class 这样的联合 class ,有没有办法构建 class 或不需要 switch 语句的移动构造函数以下代码中的移动构造函数。

class S {
    private:
        enum {CHAR, INT, DOUBLE} type; // tag
        // anonymous union
        union {
            char c;
            int n;
            double d;
        };

    public:
        // constructor if the union were to hold a character
        AS(const char c) {
            this->tag = AS::CHAR;
            this->c = c;
        }
        // constructor if the union were to hold a int
        AS(const int i) {
            this->tag = AS::INT;
            this->n = i;
        }
        // constructor if the union were to hold a double
        AS(const double d) {
            this->tag = AS::DOUBLE;
            this->d = d;
        }

        // Move constructor with switch statement
        S(S &&src) : type(std::move(src.type)) {
            switch(type) {
                case CHAR:
                    this->c = src.c);
                    src.c = 0;
                    break;
                case INT:
                    this->n = src.n;
                    src.n = 0;
                    break;
                case DOUBLE:
                    this->d = src.d;
                    src.d = 0
                    break;
                default:
                    break;
            }
        }
};

由于作为数据类型的联合指的是内部所有字段在内存中的相同位置(尽管较小字段的排序,如 space 中的 4 个字节的 char 数组 [4] 取决于系统),可以使用最大字段移动并集。这将确保您每次都移动整个并集,无论您当前正在为哪些字段使用并集。

class S {
    private:
        enum {CHAR, INT, DOUBLE} type; // tag
        // anonymous union
        union {
            char c;
            int n;
            double d;
        };
    public:
        // Move constructor with switch statement
        S(S &&src) : type(std::move(src.type)) {
             this->d = src.d;
             src.d = 0;
        }
};

不行,没有更好的办法。如果你想安全地从包含任意类型的联合中移动,你必须从最后写入的联合字段(如果有的话)中这样做。另一个相反的答案是错误的,考虑一个像

这样的例子
union SomethingLikeThisIsGoingToHappenInPractice {
  std::string what_we_actually_want_to_move;
  char what_we_do_not_care_about[sizeof(std::string)+1];
};

如果您在此处使用 'largest' 类型的移动构造函数,则必须在此处选择 char 数组,尽管移动实际上并没有真正做任何事情。如果设置了 std::string 字段,您希望移动其内部缓冲区,如果您查看 char 数组,则不会发生这种情况。还要记住,移动语义是关于 语义 ,而不是关于移动内存。如果这是问题所在,您可以始终使用 memmove 并完成它,不需要 C++11。

这甚至没有涉及从您尚未写入的联合成员中读取成为 C++ 中的 UB 的问题,即使对于基本类型,但尤其是对于 class 类型。

TL;DR 如果您发现自己处于这种情况,请使用 OP 最初提出的解决方案,不是接受的答案中的内容。


PS:当然,如果你只是移动一个只包含普通可移动事物的联合,比如原始类型,你可以只使用联合的默认移动构造函数,它只复制内存;在那种情况下,除了为了一致性之外,首先拥有一个移动构造函数真的不值得。

是的,有更好的方法。首先它必须添加 EMPTY 标签,之后使用委托复制构造函数:

class S {
    private:
        enum {EMPTY, CHAR, INT, DOUBLE} type; // tag
        // anonymous union
        union {
            char c;
            int n;
            double d;
        };

    public:
        S(){ this->type = EMPTY; }

        // constructor if the union were to hold a character
        S(const char c) {
            this->type = CHAR;
            this->c = c;
        }
        // constructor if the union were to hold a int
        S(const int i) {
            this->type = INT;
            this->n = i;
        }
        // constructor if the union were to hold a double
        S(const double d) {
            this->type = DOUBLE;
            this->d = d;
        }

        S(const S& src) = default;// default copy constructor

        // Move constructor 
        S(S&& src) 
          : S(src) // copy here
        {
          src.type = EMPTY; // mark src as empty
        }
};

当然,这个例子不是很有用,不像下面的例子,它添加了一个指向字符串的指针:

#include <cassert>
#include <iostream>
#include <memory>
#include <string>

class S {
    public:
        enum Tag {EMPTY, CHAR, INT, DOUBLE, STRING};
    private:
        Tag type; 
        // anonymous union
        union {
            char c;
            int n;
            double d;
            std::string* str;
        };

    public:
        S(){ this->type = EMPTY; }

        // constructor if the union were to hold a character
        S(const char c) {
            this->type = CHAR;
            this->c = c;
        }
        // constructor if the union were to hold a int
        S(const int i) {
            this->type = INT;
            this->n = i;
        }
        // constructor if the union were to hold a double
        S(const double d) {
            this->type = DOUBLE;
            this->d = d;
        }

        S(std::unique_ptr<std::string> ptr) {
            this->type = STRING;
            this->str = ptr.release();
        }

        std::unique_ptr<std::string> ExtractStr()
        {
           if ( this->type != STRING )
             return nullptr;

           std::string* ptr = this->str;
           this->str  = nullptr;
           this->type = EMPTY;
           return std::unique_ptr<std::string>{ptr};
        }

        Tag GetType() const
        {
          return type;
        }

    private:
        // only move is allowed for public
                     S(const S& src) = default;// default copy constructor
        S& operator = (const S& src) = default;// default copy assignment operator
    public:

        // Move constructor 
        S(S&& src) 
          : S(src) // copy here (but we copy only pointer for STRING)
        {
          src.type = EMPTY; // mark src as empty
        }

        S& operator = (S&& src)
        {
          if ( this->type == STRING )
             ExtractStr();// this call frees memory

          this->operator=(src);
          src.type = EMPTY;
          return *this;
        }

      ~S()
       {
          if ( this->type == STRING )
          {
             ExtractStr();// this call frees memory
          }
       }
};

// some test
int main()
{
  S sN(1);
  S sF(2.2);
  S x{std::move(sN)};

  std::unique_ptr<std::string> pStr1 = std::make_unique<std::string>("specially for Daniel Robertson");
  std::unique_ptr<std::string> pStr2 = std::make_unique<std::string>("example ");
  std::unique_ptr<std::string> pStr3 = std::make_unique<std::string>("~S() test");

  S xStr1(std::move(pStr1)); 
  S xStr2(std::move(pStr2)); 
  S xStr3(std::move(pStr3)); 
  x = std::move(xStr1);

  assert(xStr1.GetType() == S::EMPTY);
  assert(x.GetType() == S::STRING);

  std::string str2 = *xStr2.ExtractStr();
  std::cout << str2 << *x.ExtractStr() << std::endl; 
  return 0;
}