移动构造函数和静态数组
Move Constructors and Static Arrays
我一直在探索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;
};
- C++17中函数模板中的静态数组初始化(MSVC 2019)
- 静态数组的自由动态数组
- 如何在C++函数中声明静态 2D 数组?
- 从另一个静态常量数组初始化静态常量数组(只需少量计算)
- 基于字节数组生成静态范围整数值
- 如何在C++中删除静态数组?
- 为什么 &a 和 c++ 中的静态数组相同?
- 为什么静态数组成员变量在调用对象的实例后不显示任何内容?
- C++ 返回指向函数内定义的静态数组的指针是否有效?
- 将在堆栈上声明的元素添加到静态数组
- const_cast静态数组以添加恒常性
- C++访问静态 constexpr 数组
- 初始化类中的静态 const 数组 - C++
- 将静态字符数组中的字符分配给动态分配的字符数组 - 访问冲突
- 动态分配的数组和静态数组之间的区别
- 如何在 C++ 中使用 NULL(或 0)初始化静态字符数组
- C++编译时使用 constexpr 字符数组指针分配静态数组?
- 我可以使用 constexpr 函数声明一个静态数组吗?
- 如何在静态函数中使用成员函数数组
- 具有大的2d数组:静态int与int