如何编写move构造函数来处理未初始化的move?

时间:2019-05-19 00:37:50

标签: c++ stdvector move-semantics

我的C ++代码中有一个类,它具有自己的move构造函数。简化的版本如下所示:

$ ./bin/paticle particles.txt

frame: 0  particles: 5

1.23    2.33    4.56
1.23    2.33    5.56
1.23    2.33    6.56
1.23    2.33    7.56
1.23    2.33    8.56


frame: 1  particles: 5

2.23    2.33    4.56
2.23    3.33    5.56
2.23    4.33    6.56
2.23    5.33    7.56
2.23    6.33    8.56

在正常情况下,这可以正常工作。但是,最近我需要对这些对象进行向量处理:

class myClass {
    //In this example, myClass must manually manage allocating
    //and freeing a memory buffer.
    char *mem;

    //...
    //Regular constructor, copy constructor, etc
    //...

    myClass(myClass &&other) {
        //Swap our memory pointer with other's memory pointer
        char *tmp = other.mem;
        other.mem = mem;
        mem = tmp;
    } 

    //...
    //Destructor, other member functions, etc.
    //...
}

在遇到段错误并逐步使用gdb之后,我最终发现应该是显而易见的:如果尝试从右值引用构造对象,这可能导致在未初始化的内存上使用move构造函数

当您可能将垃圾交换到vector<myClass> v; v.reserve(10); //Make space, but do not construct v.push_back(myClass()); //Problem! 类中时,应该如何编写move构造函数?有什么方法可以检测到这一点吗?

2 个答案:

答案 0 :(得分:6)

  

当您有可能将垃圾交换到另一个类中时,应该如何编写move构造函数?有什么方法可以检测到这一点吗?

一个未初始化的对象拥有一个不确定的值,直到为其分配了另一个值[basic.indet]/1。基本上,除了为对象分配适当的值[basic.indet]/2之外,基本上不允许对它执行任何操作。由于您甚至不被允许查看对象持有的值,除非对象已被初始化或分配了值,所以不可能仅通过查看对象本身来检测对象是否已初始化(因为您已经甚至不允许看)。因此,严格来说,实际上,您并不仅仅是在“将垃圾值交换到另一个类中”,而是在调用未定义的行为。交换垃圾就是这种未定义行为通常表现出来的方式。

解决问题的方法很简单:确保始终将指针初始化为有效值,例如nullptr

class myClass {
    //In this example, myClass must manually manage allocating
    //and freeing a memory buffer.
    char *mem = nullptr;

    //...
    //Regular constructor, copy constructor, etc
    //...

    myClass(myClass &&other) {
        //Swap our memory pointer with other's memory pointer
        char *tmp = other.mem;
        other.mem = mem;
        mem = tmp;
    } 

    //...
    //Destructor, other member functions, etc.
    //...
}

考虑而不是自己实现move构造函数,例如,考虑仅使用类型为std::unique_ptr的成员,并仅依靠隐式定义的move构造函数。例如:

class myClass
{
    std::unique_ptr<char[]> mem;

    // regular constructor, copy constructor, etc.

    myClass(myClass&&) = default;

    // other member functions, etc.
};

答案 1 :(得分:2)

不要在构造函数中交换指针。那不是您编写move构造函数的方式。当两个对象都处于活动状态时,交换用于移动分配。

存在构造函数以初始化对象。这样,它们开始时的内存始终处于“未初始化”状态。因此,除非您初始化成员(或者它具有为您初始化它的默认构造函数),否则该成员的值将开始未初始化。

正确的处理方法是将指针复制到成员初始化器中,然后将另一个初始化为空。

myClass(myClass &&other) : mem(other.mem) {
    other.mem = nullptr;
}

或者,对于C ++ 14(和具有constexpr版本的C ++ 20),您可以exchange the value

myClass(myClass &&other)
  : mem(std::exchange(other.mem, nullptr))
{}