我一直在探索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>&)
,这将执行冗余复制操作。
我的问题是,正如代码中所提到的,这个使用静态分配的简单数组的特定示例,有没有办法利用移动构造函数来避免这个冗余副本?
答案 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;
};