移动构造函数和静态数组

时间:2011-10-25 06:33:22

标签: c++ c++11 rvalue-reference move-semantics move-constructor

我一直在探索C ++中Move Constructors的可能性,我想知道在下面的例子中利用这个功能有哪些方法。请考虑以下代码:

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<N>::Foo(Foo<N>&&)时调用foo,这是所需的行为。但是,如果我们没有Foo<N>::Foo(Foo<N>&&),则会调用Foo<N>::Foo(const Foo<N>&),这将执行冗余复制操作。

我的问题是,正如代码中所提到的,这个使用静态分配的简单数组的特定示例,有没有办法利用移动构造函数来避免这个冗余副本?

4 个答案:

答案 0 :(得分:21)

首先,有一般性的建议说,如果你可以提供帮助,你根本不应该写任何复制/移动构造函数,赋值运算符或析构函数,而是撰写你的类高质量的组件,反过来提供这些,允许默认生成的功能做正确的事情。 (反过来说,如果你必须写任何一个,你可能必须写下所有这些。)

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

主要的例子是管理动态内存的任何东西,其中移动操作只是复制指针并将旧对象的指针设置为零(因此旧对象的析构函数不执行任何操作)。这是一个天真的例子:

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
};

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

答案 1 :(得分:8)

在这种情况下它没用,因为int没有move-constructors。

但是,如果它们是字符串,则可能很有用,例如:

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];
};

现在,您可以避免复制移动操作的字符串。如果您完全省略所有复制/移动构造函数,我不确定符合要求的C ++ 11编译器是否会生成等效代码,抱歉。

(换句话说,我不确定std::move是否被特别定义为数组的元素移动。)

答案 2 :(得分:7)

对于你编写的类模板,接受一个移动构造函数是没有优势的。

如果成员数组是动态分配的,那将是有利的。但是如果使用普通数组作为成员,则无法优化,只能复制值。没有办法移动

答案 3 :(得分:1)

通常,当您的班级管理 资源时,会实施移动语义。因为在你的情况下,类没有管理资源,所以移动语义更像是复制语义,因为没有什么东西可以移动

为了更好地理解何时需要移动语义,请考虑使_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;
};