C++ 什么是移动语义?
我刚听完软件工程广播。大多数新特性对我来说都是有意义的,我现在对C++0x感到兴奋,除了一个。我还是不明白。。。具体是什么?假设您有一个返回实体对象的函数:C++ 什么是移动语义?,c++,c++-faq,c++11,move-semantics,C++,C++ Faq,C++11,Move Semantics,我刚听完软件工程广播。大多数新特性对我来说都是有意义的,我现在对C++0x感到兴奋,除了一个。我还是不明白。。。具体是什么?假设您有一个返回实体对象的函数: Matrix multiply(const Matrix &a, const Matrix &b); 当您编写这样的代码时: Matrix r = multiply(a, b); unique_ptr<Shape>&& flawed_attempt() // DO NOT DO THIS!
Matrix multiply(const Matrix &a, const Matrix &b);
当您编写这样的代码时:
Matrix r = multiply(a, b);
unique_ptr<Shape>&& flawed_attempt() // DO NOT DO THIS!
{
unique_ptr<Shape> very_bad_idea(new Square);
return std::move(very_bad_idea); // WRONG!
}
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(parameter) // error
{}
};
然后,普通C++编译器将为
,然后丢弃临时值而不必对其进行破坏r
如果要复制的对象在堆上分配额外内存以存储其内部表示,这一点尤其重要(可能与上面的
矩阵
示例类似)。复制构造函数必须创建内部表示的完整副本,或者内部使用引用计数和写时复制语义。移动构造函数将不使用堆内存,只在矩阵
对象中复制指针。假设您有一个返回实体对象的函数:
Matrix multiply(const Matrix &a, const Matrix &b);
当您编写这样的代码时:
Matrix r = multiply(a, b);
unique_ptr<Shape>&& flawed_attempt() // DO NOT DO THIS!
{
unique_ptr<Shape> very_bad_idea(new Square);
return std::move(very_bad_idea); // WRONG!
}
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(parameter) // error
{}
};
然后,普通C++编译器将为
,然后丢弃临时值而不必对其进行破坏r
如果要复制的对象在堆上分配额外内存以存储其内部表示,这一点尤其重要(可能与上面的
矩阵
示例类似)。复制构造函数必须创建内部表示的完整副本,或者内部使用引用计数和写时复制语义。移动构造函数将不使用堆内存,只在矩阵
对象中复制指针。这类似于复制语义,但不必复制所有数据来从“移动”的对象中窃取数据。这类似于复制语义,但是你不必复制所有的数据就可以从被“移动”的对象中窃取数据。你知道复制语义是什么意思吗?这意味着您拥有可复制的类型,对于用户定义的类型,您可以显式地编写复制构造函数和赋值运算符,或者编译器隐式地生成它们。这可以复印一份
移动语义基本上是一种用户定义的类型,其构造函数接受一个非常量的r值引用(使用&&(是两个符号)的新引用类型),这称为移动构造函数,赋值运算符也是如此。那么move构造函数做什么呢?它不是从源参数复制内存,而是将内存从源参数“移动”到目标参数
你想什么时候做?例如,std::vector创建了一个临时std::vector,并从函数返回它,比如:
std::vector<foo> get_foos();
std::vector get_foos();
如果(在C++0x中)std::vector有一个移动构造函数而不是复制它,那么当函数返回时,复制构造函数将产生开销,只需设置它的指针并将动态分配的内存“移动”到新实例。这有点像std::auto_ptr的所有权转移语义。你知道复制语义是什么意思吗?这意味着您拥有可复制的类型,对于用户定义的类型,您可以显式地编写复制构造函数和赋值运算符,或者编译器隐式地生成它们。这可以复印一份 移动语义基本上是一种用户定义的类型,其构造函数接受一个非常量的r值引用(使用&&(是两个符号)的新引用类型),这称为移动构造函数,赋值运算符也是如此。那么move构造函数做什么呢?它不是从源参数复制内存,而是将内存从源参数“移动”到目标参数 你想什么时候做?例如,std::vector创建了一个临时std::vector,并从函数返回它,比如:
std::vector<foo> get_foos();
std::vector get_foos();
如果(在C++0x中)std::vector有一个移动构造函数而不是复制它,那么当函数返回时,复制构造函数将产生开销,只需设置它的指针并将动态分配的内存“移动”到新实例。这有点像std::auto_ptr的所有权转移语义。移动语义基于右值引用
右值是一个临时对象,它将在表达式末尾被销毁。在当前C++中,rValk只绑定到<代码> const 引用。C++1x将允许非
常量
右值引用,拼写为T&
,它们是对右值对象的引用。由于右值将在表达式末尾消失,因此可以窃取其数据。将其数据移动到另一个对象中,而不是将其复制到另一个对象中
class X {
public:
X(X&& rhs) // ctor taking an rvalue reference, so-called move-ctor
: data_()
{
// since 'x' is an rvalue object, we can steal its data
this->swap(std::move(rhs));
// this will leave rhs with the empty data
}
void swap(X&& rhs);
// ...
};
// ...
X f();
X x = f(); // f() returns result as rvalue, so this calls move-ctor
在上面的代码中,使用x
的复制构造函数,将f()
的结果复制到x
中。如果编译器支持移动语义,并且X
具有移动构造函数,则会调用该构造函数。因为它的rhs
参数是一个右值,我们知道它不再需要了,我们可以窃取它的值。因此,该值被从
f()
返回的未命名临时值移动到x
(而初始化为空x
的x的数据被移动到临时值中,
auto_ptr<Shape> a(new Triangle);
+---------------+
| triangle data |
+---------------+
^
|
|
|
+-----|---+
| +-|-+ |
a | p | | | |
| +---+ |
+---------+
auto_ptr<Shape> b(a);
+---------------+
| triangle data |
+---------------+
^
|
+----------------------+
|
+---------+ +-----|---+
| +---+ | | +-|-+ |
a | p | | | b | p | | | |
| +---+ | | +---+ |
+---------+ +---------+
auto_ptr(auto_ptr& source) // note the missing const
{
p = source.p;
source.p = 0; // now the source no longer owns the object
}
auto_ptr<Shape> a(new Triangle); // create triangle
auto_ptr<Shape> b(a); // move a into b
double area = a->area(); // undefined behavior
auto_ptr<Shape> make_triangle()
{
return auto_ptr<Shape>(new Triangle);
}
auto_ptr<Shape> c(make_triangle()); // move temporary into c
double area = make_triangle()->area(); // perfectly safe
auto_ptr<Shape> variable(expression);
double area = expression->area();
auto_ptr<Shape> c(make_triangle());
^ the moved-from temporary dies right here
lvalue const lvalue rvalue const rvalue
---------------------------------------------------------
X& yes
const X& yes yes yes yes
X&& yes
const X&& yes yes
void some_function(std::string&& r);
some_function("hello world");
template<typename T>
class unique_ptr
{
T* ptr;
public:
T* operator->() const
{
return ptr;
}
T& operator*() const
{
return *ptr;
}
explicit unique_ptr(T* p = nullptr)
{
ptr = p;
}
~unique_ptr()
{
delete ptr;
}
unique_ptr(unique_ptr&& source) // note the rvalue reference
{
ptr = source.ptr;
source.ptr = nullptr;
}
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // error
unique_ptr<Shape> c(make_triangle()); // okay
unique_ptr& operator=(unique_ptr&& source) // note the rvalue reference
{
if (this != &source) // beware of self-assignment
{
delete ptr; // release the old resource
ptr = source.ptr; // acquire the new resource
source.ptr = nullptr;
}
return *this;
}
};
unique_ptr& operator=(unique_ptr source) // note the missing reference
{
std::swap(ptr, source.ptr);
return *this;
}
};
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // still an error
unique_ptr<Shape> c(std::move(a)); // okay
expressions
/ \
/ \
/ \
glvalues rvalues
/ \ / \
/ \ / \
/ \ / \
lvalues xvalues prvalues
unique_ptr<Shape> make_triangle()
{
return unique_ptr<Shape>(new Triangle);
} \-----------------------------/
|
| temporary is moved into c
|
v
unique_ptr<Shape> c(make_triangle());
unique_ptr<Shape> make_square()
{
unique_ptr<Shape> result(new Square);
return result; // note the missing std::move
}
unique_ptr<Shape>&& flawed_attempt() // DO NOT DO THIS!
{
unique_ptr<Shape> very_bad_idea(new Square);
return std::move(very_bad_idea); // WRONG!
}
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(parameter) // error
{}
};
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(std::move(parameter)) // note the std::move
{}
};
X::X(const X&); // copy constructor
X& X::operator=(const X&); // copy assignment operator
X::~X(); // destructor
X::X(X&&); // move constructor
X& X::operator=(X&&); // move assignment operator
X& X::operator=(X source) // unified assignment operator
{
swap(source); // see my first answer for an explanation
return *this;
}
template<typename T>
void foo(T&&);
foo(make_triangle()); // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a); // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&
#include <type_traits>
template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type
foo(T&&);
template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
class A
{
int i, *p;
public:
A(const A& a) : i(a.i), p(new int(*a.p)) {}
~A() { delete p; }
};
class A
{
int i, *p;
public:
// Movement of an object inside a copy constructor.
A(const A& a) : i(a.i), p(a.p)
{
a.p = nullptr; // pointer invalidated.
}
~A() { delete p; }
// Deleting NULL, 0 or nullptr (address 0x0) is safe.
};
void heavyFunction(HeavyType());
class A
{
int i, *p;
public:
// Copy
A(const A& a) : i(a.i), p(new int(*a.p)) {}
// Movement (&& means "rvalue reference to")
A(A&& a) : i(a.i), p(a.p)
{
a.p = nullptr;
}
~A() { delete p; }
};
class Heavy
{
bool b_moved;
// staff
public:
A(const A& a) { /* definition */ }
A(A&& a) : // initialization list
{
a.b_moved = true;
}
~A() { if (!b_moved) /* destruct object */ }
};
void some_function(A&& a)
{
other_function(a);
}
other_function(std::move(a));
template<typename T>
void some_function(T&& a)
{
other_function(std::forward<T>(a));
}
`A& && == A&`
`A&& && == A&&`
T f(T o) { return o; }
//^^^ new object constructed
T b = f(a);
//^ new object constructed
// Copy constructor
T::T(T &old) {
copy_data(m_a, old.m_a);
copy_data(m_b, old.m_b);
copy_data(m_c, old.m_c);
}
// Move constructor
T::T(T &&old) noexcept {
m_a = std::move(old.m_a);
m_b = std::move(old.m_b);
m_c = std::move(old.m_c);
}
Vector operator+(const Vector& a, const Vector& b)
{
if (a.size()!=b.size())
throw Vector_siz e_mismatch{};
Vector res(a.size());
for (int i=0; i!=a.size(); ++i)
res[i]=a[i]+b[i];
return res;
}
class Vector {
// ...
Vector(const Vector& a); // copy constructor
Vector& operator=(const Vector& a); // copy assignment
Vector(Vector&& a); // move constructor
Vector& operator=(Vector&& a); // move assignment
};
Vector::Vector(Vector&& a)
:elem{a.elem}, // "grab the elements" from a
sz{a.sz}
{
a.elem = nullptr; // now a has no elements
a.sz = 0;
}