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
如果要复制的对象在堆上分配额外内存以存储其内部表示,这一点尤其重要(可能与上面的
矩阵
示例类似)。复制构造函数必须创建内部表示的完整副本,或者内部使用引用计数和写时复制语义。移动构造函数将不使用堆内存,只在矩阵
对象中复制指针。这类似于复制语义,但不必复制所有数据来从被“移动”的对象中窃取数据。你知道复制语义是什么意思吗?这意味着您拥有可复制的类型,对于用户定义的类型,您可以显式地编写复制构造函数和赋值运算符,或者编译器隐式地生成它们。这可以复印一份
移动语义基本上是一种用户定义的类型,其构造函数接受一个非常量的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
数据被移动到临时值中,该临时值将在赋值后被销毁) 如果你真的对移动语义的深入解释感兴趣,我强烈建议你阅读关于它们的原始论文
它非常容易理解,易于阅读,为它们提供的好处提供了一个很好的案例。上还有其他关于移动语义的最新文章,但这篇文章可能是最直接的,因为它从顶层的角度来处理问题,并且没有深入到语言的细节。我发现通过示例代码最容易理解移动语义。让我们从一个非常简单的字符串类开始,该类只保存指向堆分配内存块的指针:
#include <cstring>
#include <algorithm>
class string
{
char* data;
public:
string(const char* p)
{
size_t size = std::strlen(p) + 1;
data = new char[size];
std::memcpy(data, p, size);
}
复制构造函数定义复制字符串对象的含义。参数const-string&它绑定到string类型的所有表达式,允许您在以下示例中进行复制:
string a(x); // Line 1
string b(x + y); // Line 2
string c(some_function_returning_a_string()); // Line 3
现在是对移动语义的关键洞察。请注意,只有在我们复制x
的第一行中,这个深度副本才是真正必要的,因为我们可能希望稍后检查x
,如果x
发生了某种变化,我们会非常惊讶。你注意到我刚才说了三次(如果你包括这个句子,就说四次)并且每次都是同一个对象吗?我们称x
等表达式为“左值”
第2行和第3行中的参数不是左值,而是右值,因为底层字符串对象没有名称,因此客户端无法在稍后的时间点再次检查它们。
右值表示在下一个分号处被销毁的临时对象(更准确地说:在词汇上包含右值的完整表达式的末尾)。这一点很重要,因为在b
和c
的初始化过程中,我们可以对源字符串执行我们想要的任何操作,而客户机无法区分它们
C++0x引入了一种称为“右值引用”的新机制,
允许我们通过函数重载检测右值参数。我们所要做的就是编写一个带有右值引用参数的构造函数。在该构造函数中,我们可以对源代码执行任何我们想要的操作,只要我们将其保留在
string& operator=(string that)
{
std::swap(data, that.data);
return *this;
}
};
class cannot_benefit_from_move_semantics
{
int a; // moving an int means copying an int
float b; // moving a float means copying a float
double c; // moving a double means copying a double
char d[64]; // moving a char array means copying a char array
// ...
};
{
std::auto_ptr<Shape> a(new Triangle);
// ...
// arbitrary code, could throw exceptions
// ...
} // <--- when a goes out of scope, the triangle is deleted automatically
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;
}