拥有指针成员的 C++ 移动构造函数
C++ move constructor for an owning pointer member
我问的问题看起来微不足道,但我遇到了问题。为了便于解释,让我们假设一个像这样的结构:
class MyClass{
int* m_Number;
public:
int value() const {return *m_Number;}
void setValue(int val){*m_Number=val;}
MyClass() : m_Number(new int(3)){}
~MyClass() {if(m_Number) delete m_Number;}
MyClass(const MyClass& other):m_Number(new int(*other.m_Number)){}
MyClass& operator=(const MyClass& other){if(m_Number) *m_Number=*other.m_Number; else m_Number=new int(*other.m_Number); return *this;}
MyClass& operator=(MyClass&& other){std::swap(m_Number,other.m_Number); return *this;}
MyClass(MyClass&& other){
//????
}
}
我应该把什么放在那里?
我的选择是:
1)
MyClass(MyClass&& other)
:m_Number(other.m_Number)
{
other.m_Number=nullptr;
}
但是从中移出的对象不处于有效状态。调用 value() 应该 return 一些有效但未确定的东西,而在这里我只是段错误。我可以在 value() 和 setValue() 中检查 m_Number,但你意识到这对实际代码来说是一个巨大的拖累。
2)
MyClass(MyClass&& other)
:m_Number(other.m_Number)
{
other.m_Number= new int(3);
}
但是可以抛出的移动构造函数是行不通的(或者至少据我所知)并且它也是性能增强的拖累,实际上这段代码与复制构造函数相同或更差。
你怎么看?
我错过了什么吗?
是否有首选方式?
提前致谢
编辑:这个问题得到了std委员会召集人的回答,根本不同意post的回答。你可以在这篇文章中找到它 https://herbsutter.com/2020/02/17/move-simply/
不要使用原始拥有指针,将你的 class 转换为使用 std::unique_ptr
,你所有的问题都会消失。
首先,没有理由在这里使用 new
和 delete
,您应该使用 make_unique<int>
来创建对象并使用 unique_ptr<int>
来自动管理它。但这并不能解决移动构造函数可以做什么的问题。除了您建议的两个选项外,还有一些其他选项:
3) 不要给它一个移动构造函数,让它保持可复制状态
4) 不允许在移出对象上调用 value
或 setValue
的文档,并使用空指针保留移出对象。取决于程序中发生移动的位置可能没问题。如果从不访问移出的对象,一切正常。
4a) 同上,但添加完整性检查以防万一:
int value() const {
assert(m_Number != nullptr);
return *m_Number;
}
或:
int value() const {
if (m_Number == nullptr)
throw std::logic_error("accessed a moved-from object");
return *m_Number;
}
5) 添加检查 setValue
以使用新的 int
重新初始化对象(如果它当前为 null),并使 value
return 一些默认值:
int value() const { return m_Number ? *m_Number : 0; }
void setValue(int val) {
if (!m_Number)
m_Number = new int(val);
else
*m_Number = val;
}
您正在取消引用 .value()
调用中的指针。如果 m_Number
无效,您将始终出现段错误。
您对移动构造函数的第一个解决方案是正确的,您应该将 'other' 对象设置为默认状态。为了解决这个问题,你可以让你的 .value()
方法抛出,或者 return 在资源不存在的情况下的默认值。
您的析构函数已经考虑了 null 情况,因此请确保它的其余部分也考虑了它。
如果修改 MyClass
使得 m_Number = nullptr
是一个有效状态是合理的(并且如果它是默认状态,那将是最佳实践),那么您的第一种方法可能是最好的状态也一样)。我认为如果与 MyClass
相关联的无堆内存不是有效状态,您应该在 std::unique_ptr
内分配它,并传递指向它的原始指针而不是执行移动构造函数.
如果 1.
不是一个选项,这是一个合理的方法。虽然分配不需要的内存肯定会对性能造成影响,但它非常小,特别是考虑到您正在构建一个 class 作为此操作的一部分(它本身需要内存)。如果它在您的示例中需要一小块内存,则内存分配器可能会在不需要系统调用的情况下从其自己的池中提取它。如果它是一大块内存(正如我所期望的那样,因为你正在实现移动语义),那么在大多数现代操作系统上它将是一个惰性分配(因此它仍然比复制构造函数更好)。
我问的问题看起来微不足道,但我遇到了问题。为了便于解释,让我们假设一个像这样的结构:
class MyClass{
int* m_Number;
public:
int value() const {return *m_Number;}
void setValue(int val){*m_Number=val;}
MyClass() : m_Number(new int(3)){}
~MyClass() {if(m_Number) delete m_Number;}
MyClass(const MyClass& other):m_Number(new int(*other.m_Number)){}
MyClass& operator=(const MyClass& other){if(m_Number) *m_Number=*other.m_Number; else m_Number=new int(*other.m_Number); return *this;}
MyClass& operator=(MyClass&& other){std::swap(m_Number,other.m_Number); return *this;}
MyClass(MyClass&& other){
//????
}
}
我应该把什么放在那里? 我的选择是:
1)
MyClass(MyClass&& other)
:m_Number(other.m_Number)
{
other.m_Number=nullptr;
}
但是从中移出的对象不处于有效状态。调用 value() 应该 return 一些有效但未确定的东西,而在这里我只是段错误。我可以在 value() 和 setValue() 中检查 m_Number,但你意识到这对实际代码来说是一个巨大的拖累。
2)
MyClass(MyClass&& other)
:m_Number(other.m_Number)
{
other.m_Number= new int(3);
}
但是可以抛出的移动构造函数是行不通的(或者至少据我所知)并且它也是性能增强的拖累,实际上这段代码与复制构造函数相同或更差。
你怎么看?
我错过了什么吗?
是否有首选方式?
提前致谢
编辑:这个问题得到了std委员会召集人的回答,根本不同意post的回答。你可以在这篇文章中找到它 https://herbsutter.com/2020/02/17/move-simply/
不要使用原始拥有指针,将你的 class 转换为使用 std::unique_ptr
,你所有的问题都会消失。
首先,没有理由在这里使用 new
和 delete
,您应该使用 make_unique<int>
来创建对象并使用 unique_ptr<int>
来自动管理它。但这并不能解决移动构造函数可以做什么的问题。除了您建议的两个选项外,还有一些其他选项:
3) 不要给它一个移动构造函数,让它保持可复制状态
4) 不允许在移出对象上调用 value
或 setValue
的文档,并使用空指针保留移出对象。取决于程序中发生移动的位置可能没问题。如果从不访问移出的对象,一切正常。
4a) 同上,但添加完整性检查以防万一:
int value() const {
assert(m_Number != nullptr);
return *m_Number;
}
或:
int value() const {
if (m_Number == nullptr)
throw std::logic_error("accessed a moved-from object");
return *m_Number;
}
5) 添加检查 setValue
以使用新的 int
重新初始化对象(如果它当前为 null),并使 value
return 一些默认值:
int value() const { return m_Number ? *m_Number : 0; }
void setValue(int val) {
if (!m_Number)
m_Number = new int(val);
else
*m_Number = val;
}
您正在取消引用 .value()
调用中的指针。如果 m_Number
无效,您将始终出现段错误。
您对移动构造函数的第一个解决方案是正确的,您应该将 'other' 对象设置为默认状态。为了解决这个问题,你可以让你的 .value()
方法抛出,或者 return 在资源不存在的情况下的默认值。
您的析构函数已经考虑了 null 情况,因此请确保它的其余部分也考虑了它。
如果修改
MyClass
使得m_Number = nullptr
是一个有效状态是合理的(并且如果它是默认状态,那将是最佳实践),那么您的第一种方法可能是最好的状态也一样)。我认为如果与MyClass
相关联的无堆内存不是有效状态,您应该在std::unique_ptr
内分配它,并传递指向它的原始指针而不是执行移动构造函数.如果
1.
不是一个选项,这是一个合理的方法。虽然分配不需要的内存肯定会对性能造成影响,但它非常小,特别是考虑到您正在构建一个 class 作为此操作的一部分(它本身需要内存)。如果它在您的示例中需要一小块内存,则内存分配器可能会在不需要系统调用的情况下从其自己的池中提取它。如果它是一大块内存(正如我所期望的那样,因为你正在实现移动语义),那么在大多数现代操作系统上它将是一个惰性分配(因此它仍然比复制构造函数更好)。