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;
}
有没有更好的方法来为类联合 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;
}