移动构造函数和静态数组

Move Constructors and Static Arrays

本文关键字:数组 静态 构造函数 移动      更新时间:2023-10-16

我一直在探索c++中移动构造函数的可能性,我想知道在下面的例子中有哪些方法可以利用这一特性。考虑以下代码:

template<unsigned int N>
class Foo {
public:
    Foo() {
        for (int i = 0; i < N; ++i) _nums[i] = 0;
    }
    Foo(const Foo<N>& other) {
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }
    Foo(Foo<N>&& other) {
        // ??? How can we take advantage of move constructors here?
    }
    // ... other methods and members
    virtual ~Foo() { /* no action required */ }
private:
    int _nums[N];
};
Foo<5> bar() {
    Foo<5> result;
    // Do stuff with 'result'
    return result;
}
int main() {
    Foo<5> foo(bar());
    // ...
    return 0;
}

在上面的例子中,如果我们跟踪程序(使用msvc++ 2011),我们看到在构造foo时调用Foo<N>::Foo(Foo<N>&&),这是期望的行为。然而,如果我们没有Foo<N>::Foo(Foo<N>&&), Foo<N>::Foo(const Foo<N>&)将被调用,这将做一个冗余的复制操作。

我的问题是,正如代码中所指出的,在这个使用静态分配的简单数组的特定示例中,是否有任何方法可以利用move构造函数来避免这种冗余复制?

首先,有一种通用的建议认为,如果可以的话,你不应该编写任何复制/移动构造函数、赋值操作符或析构函数,而是组成类的高质量组件,这些组件反过来提供这些,允许默认生成的函数做正确的事情。(相反的含义是,如果你必须写其中的任何一个,你可能必须写所有的。)

所以问题归结为"哪个单责任组件类可以利用move语义?"一般的答案是:任何管理资源的。关键是move构造函数/赋值器只会将资源重新定位到新对象中,并使旧对象无效,从而避免了(假定昂贵或不可能的)新分配和资源的深度复制。

最典型的例子是管理动态内存的任何东西,其中move操作只是复制指针并将旧对象的指针设置为零(因此旧对象的析构函数什么也不做)。下面是一个简单的例子:
class MySpace
{
  void * addr;
  std::size_t len;
public:
  explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }
  ~MySpace() { ::operator delete(addr); }
  MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
  { /* copy memory */ }
  MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
  { rhs.len = 0; rhs.addr = 0; }
  // ditto for assignment
};

关键是任何复制/移动构造函数都将对成员变量进行完整的复制;只有当这些变量本身是句柄或指向资源的指针时,您才能避免复制资源,因为协议认为移动的对象不再被认为是有效的,并且您可以自由地从中窃取。如果没有什么可偷的,那么搬家也没有什么好处。

在这种情况下,它没有用,因为int没有move-构造函数。

但是,如果这些是字符串,则可能有用,例如:

template<unsigned int N>
class Foo {
public:
    // [snip]
    Foo(Foo<N>&& other) {
        // move each element from other._nums to _nums
        std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
    }
    // [snip]
private:
    std::string _nums[N];
};

现在你可以避免复制一个移动就可以完成的字符串。如果你完全省略所有的copy-/move-构造函数,我不确定一个符合标准的c++ 11编译器是否会生成等效的代码,对不起。

(换句话说,我不确定std::move是否专门定义为对数组进行元素明智移动。)

对于您编写的类模板,使用move构造函数没有任何好处。

如果成员数组是动态分配的,这将是一个优势。但是使用普通数组作为成员,没有什么需要优化的,您只能复制值。无法移动

通常,move-semantic是在类管理资源时实现的。由于在您的示例中,类不管理资源,因此移动语义将更像复制语义,因为没有要移动

为了更好地理解何时需要移动语义,考虑将_nums设置为指针,而不是数组:

template<unsigned int N>
class Foo {
public:
    Foo() 
    {
        _nums = new int[N](); //allocate and zeo-initialized
    }
    Foo(const Foo<N>& other) 
    {
        _nums = new int[N];
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }
    Foo(Foo<N>&& other) 
    {
         _nums = other._nums; //move the resource
         other._nums=0; //make it null
    }
    Foo<N> operator=(const Foo<N> & other); //implement it!
    virtual ~Foo() { delete [] _nums; }
private:
    int *_nums;
};