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

时间:2015-03-12 00:57:16

标签: c++ c++11 unions move-constructor

是否有更好的方法为类似联合的类构建移动构造函数?如果我在下面的代码中有类似类的类似类的类,有没有办法构建类或移动构造函数,它不需要像下面代码中的移动构造函数那样的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;
            }
        }
};

3 个答案:

答案 0 :(得分:3)

没有,没有更好的方法。如果要从包含任意类型的联合安全地移出,则必须从最后写入联合的字段(如果有)中进行。另一个相反的答案是错误的,请考虑类似

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

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

这甚至不涉及从尚未编写为C ++的UB的工会成员中读取的问题,即使对于原始类型,尤其是对于类类型也是如此。

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


PS:当然,如果您只移动一个仅包含可移动的东西(例如原始类型)的联合,则可以对联合使用默认的move构造函数,它仅复制内存;在那种情况下,除了出于一致性的考虑之外,首先没有移动构造器确实不值得。

答案 1 :(得分:0)

由于作为数据类型的联合在内存中的所有字段中都指向内存中的相同位置(尽管较小字段的排序,例如该空间中char字符[4]的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;
        }
};

答案 2 :(得分:0)

是的,有更好的方法。首先它必须添加 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;
}