C++ 什么是移动语义?

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!

我刚听完软件工程广播。大多数新特性对我来说都是有意义的,我现在对C++0x感到兴奋,除了一个。我还是不明白。。。具体是什么?

假设您有一个返回实体对象的函数:

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++编译器将为乘法()/CODE的结果创建临时对象,调用复制构造函数初始化 R>代码>,然后破坏临时返回值。C++0x中的移动语义允许调用“移动构造函数”,通过复制其内容来初始化

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