0
点赞
收藏
分享

微信扫一扫

【python使用 Pillow 库】图片合成为 GIF 动画

目录

一. 问题引入

二. 右值引用

1. lvalue/rvalue/prvalue/xvalue

1.1 表达式与对象的概念

1.2 左值与右值 

2. moving semantics

2.1 显示绑定

2.2 Move constructors

2.3 Move assignment operator

2.4 实例分析

// TODO

Quiz

REF


本章简单介绍下move语义的“来龙去脉”。

一. 问题引入

如下,先来看看拷贝赋值的一个过程。

 到此,“移动语义”就被引入了。接下来的事情就是介绍C++是如何定义“移动语义”的。

二. 右值引用

有了一个大概的概念,先来看看如下代码及运行结果。 

#include <iostream>

using namespace std;

void foo(int &&x)
{
    cout << "call && rvalue reference"<< endl;
}

void foo(int &x)
{
    cout << "call & lvalue reference" << endl;
}

int main() {
    int i = 1;
    int &lv = i; // 左值引用
    int &&rv = 2; // 右值引用
    
    foo(1); // call && rvalue reference
    foo(i); // call & lvalue reference
    foo(lv); // all & lvalue reference
    foo(rv); // call & lvalue reference
    
    return 0;
}

运行结果及分析如下: 

// 因为foo(1);中的'1'是字面量,字面量为纯右值,这里不能用纯右值去初始化一个左值引用,所以会调用void foo(int &&x){}
call && rvalue reference

// 因为foo(i);中的i是变量名,即是一个左值,所以会调用void foo(int &x){}
call & lvalue reference

// 因为foo(lv);中的lv是一个左值引用,所以会调用void foo(int &x){}
call & lvalue reference

// 虽然foo(rv);中的rv是一个右值引用,但是因为rv是一个变量名,所以也是一个左值,进而会调用void foo(int &x){} --从这里也可知道 右值引用其实也是一个左值
call & lvalue reference

接下来再来分别介绍下上面提到的4个概念:左值、右值、左值引用、右值引用。 

1. lvalue/rvalue/prvalue/xvalue

1.1 表达式与对象的概念

Q:什么叫表达式?

A:《C++ Primer》里是这样解释的:“表达式(expression)最小的计算单元。一个表达式包含一个或多个运算对象,通常还包含一个或多个运算符。表达式求值会产生一个结果。例如,假设i和j是int对象,则i+j是一个表达式,它产生两个int值的和”。简单来说,表达式就是运算符和运算对象组成的序列,能指明一个计算且能够产生结果或作用。例如,“1+2”是表达式,“std::count << 1”也是表达式,但是 “std::count << 1;”则是一个语句。基本表达式(Primary Expression)有如下几种:

这里顺带再补充下什么叫对象。

一般来讲,对象要有size,生命周期,类型,值等属性。如下实体就不是对象:引用,函数,枚举项,类型,类的非静态成员,模板,类或函数模板的特化,命名空间,形参包,和 this。

引用(reference)只是已有对象或函数的别名,编译器不必为其分配内存,因此也不存在引用数组,引用的引用,指向引用的指针等。 

1.2 左值与右值 

上面讲了一大堆,其实能记住的也就是下面几句话:

1. 任何有名字的表达式都是左值(枚举例外);

2. 字面量除了字符串字面量以外,均为纯右值。而字符串字面量是一个左值,类型为 const char 数组。

3. 右值的关键字在”临时“,例如,

(1) 求值结果相当于字面量或匿名临时对象,例如 1+2就是右值

(2) 非引用返回的临时变量、运算表达式产生的临时变量、 原始字面量、Lambda 表达式都属于纯右值。

value category是否是临时的是否占内存是否能被取地址是否能被赋值是否能用来初始化引用
lvalue(left/locator value)NYYY

Y

(例如

double d = 2.0;
double& rd = d;)  
rvalueprvalue(pure right value)
纯右值
YN例如 Foo f = f(); 这里的f()就是一个纯右值。NN

Y

(例如const int& r1 = 1; 1先被materialize,由prvalue转为xvalue)

xvalue(eXpiring value)

将亡值

YY右值不必占内存,并不是说不能占内存。例如f()这个函数是右值也是临时的,但是它必须占内存,因为需要使用f().X访问成员。NN仅能初始化const &
因为lvalue和xvalue都占内存,因此有的地方也把lvalue和xvalue统称为glvalue(generalized lvalue,泛化的左值)。
const int& r1 = 1; 1由prvalue转为xvalue

2. moving semantics

2.1 显示绑定

int &&r1 = 1;// 正确
int &&r2 = r1; // 错误,因为r1是左值

需要改成如下 

int&& r2 = std::move(r1); // 正确

用标准库函数std::move()来实现移动语义的自动转换。

2.2 Move constructors

Move constructors - cppreference.com

移动构造的语法规则如下:

可以使用标准库函数std::move()来实现自动转换。

// C++ reference 例子

#include <iomanip>
#include <iostream>
#include <string>
#include <utility>
 
struct A
{
    std::string s;
    int k;
 
    A() : s("test"), k(-1) {}
    A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; }
    A(A&& o) noexcept :
        s(std::move(o.s)),       // explicit move of a member of class type
        k(std::exchange(o.k, 0)) // explicit move of a member of non-class type
    {}
};
 
A f(A a)
{
    return a;
}
 
struct B : A
{
    std::string s2;
    int n;
    // implicit move constructor B::(B&&)
    // calls A's move constructor
    // calls s2's move constructor
    // and makes a bitwise copy of n
};
 
struct C : B
{
    ~C() {} // destructor prevents implicit move constructor C::(C&&)
};
 
struct D : B
{
    D() {}
    ~D() {}           // destructor would prevent implicit move constructor D::(D&&)
    D(D&&) = default; // forces a move constructor anyway
};
 
int main()
{
    std::cout << "Trying to move A\n";
    A a1 = f(A()); // return by value move-constructs the target
                   // from the function parameter
 
    std::cout << "Before move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
 
    A a2 = std::move(a1); // move-constructs from xvalue
    std::cout << "After move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
 
 
    std::cout << "\nTrying to move B\n";
    B b1;
 
    std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n";
 
    B b2 = std::move(b1); // calls implicit move constructor
    std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n";
 
 
    std::cout << "\nTrying to move C\n";
    C c1;
    C c2 = std::move(c1); // calls copy constructor
 
    std::cout << "\nTrying to move D\n";
    D d1;
    D d2 = std::move(d1);
}

运行结果如下 

2.3 Move assignment operator

Move assignment operator - cppreference.com

可以使用标准库函数std::move()来实现自动转换。

#include <iostream>
#include <string>
#include <utility>
 
struct A
{
    std::string s;
 
    A() : s("test") {}
 
    A(const A& o) : s(o.s) { std::cout << "move failed!\n"; }
 
    A(A&& o) : s(std::move(o.s)) {}
 
    A& operator=(const A& other)
    {
         s = other.s;
         std::cout << "copy assigned\n";
         return *this;
    }
 
    A& operator=(A&& other)
    {
         s = std::move(other.s);
         std::cout << "move assigned\n";
         return *this;
    }
};
 
A f(A a) { return a; }
 
struct B : A
{
    std::string s2; 
    int n;
    // implicit move assignment operator B& B::operator=(B&&)
    // calls A's move assignment operator
    // calls s2's move assignment operator
    // and makes a bitwise copy of n
};
 
struct C : B
{
    ~C() {} // destructor prevents implicit move assignment
};
 
struct D : B
{
    D() {}
    ~D() {} // destructor would prevent implicit move assignment
    D& operator=(D&&) = default; // force a move assignment anyway 
};
 
int main()
{
    A a1, a2;
    std::cout << "Trying to move-assign A from rvalue temporary\n";
    a1 = f(A()); // move-assignment from rvalue temporary
    std::cout << "Trying to move-assign A from xvalue\n";
    a2 = std::move(a1); // move-assignment from xvalue
 
    std::cout << "\nTrying to move-assign B\n";
    B b1, b2;
    std::cout << "Before move, b1.s = \"" << b1.s << "\"\n";
    b2 = std::move(b1); // calls implicit move assignment
    std::cout << "After move, b1.s = \"" << b1.s << "\"\n";
 
    std::cout << "\nTrying to move-assign C\n";
    C c1, c2;
    c2 = std::move(c1); // calls the copy assignment operator
 
    std::cout << "\nTrying to move-assign D\n";
    D d1, d2;
    d2 = std::move(d1);
}

运行结果如下:

2.4 实例分析

https://gcc.godbolt.org/z/1q9qcK9Pb

#include <iostream>
#include <cstring>
#include <utility>
using namespace std;

class String {
    char * content;
public:
    String(const char * str = "") {
        if (str) {
            content = new char[strlen(str) + 1];
            strcpy(content, str);
        }
        cout << (void*)content << " : ctor\n";
    }

    String(const String &s) {
        content = new char[strlen(s.content) + 1];
        strcpy(content, s.content);
        cout << (void*)content << " : copy ctor\n";
    }

    String(String &&s) noexcept
        : content(std::exchange(s.content, nullptr)) {   
        cout << (void*)content << " : move ctor\n";
        cout << (bool)s.content << ", " << (void*)s.content << "\n";
    }

    String & operator=(const String &s) {
        if (this == &s)     return *this;
        if (!content || strlen(content) != strlen(s.content)) {
            delete[] content;
            content = new char[strlen(s.content) + 1];
        }
        strcpy(content, s.content);
        cout << (void*)content << " : copy assignment\n";
        return *this;
    }

    String & operator=(String && s) noexcept {
        std::swap(content, s.content);
        cout << (void*)content << " : move assignment\n";
        return *this;
    }

    ~String() {
        cout << (void*)content << " : dtor\n";
        delete[] content;
    }
};

class Msg {
    String content;
    unsigned from, to;
public:
    explicit Msg(const char * content, unsigned from, unsigned to) : 
        content(content), from(from), to(to) {}
};

int main() {
    Msg a("msg", 1, 2);
    Msg b = a;              // copy ctor
    Msg c = std::move(a);   // move ctor
    c = b;                  // copy assign
    c = std::move(b);       // move assign
    return 0;
}

运行结果

// TODO

Quiz

int& r = 1; // 错误,因为1是rvalue,是无法取地址的,因此这样写是非法的

const int& r = 1; // 正确,因为1是先由prvalue转为xvalue,xvalue是占内存的。

再分析下如下“1 = i;”错误的原因

int i;

i = 1;  // 正确

1 = i;  // 错误

REF

1. 《Modern C++ Tutorial: C++ 11/14/17/20 On the Fly》

2. 《C++ Primer》(5th)

3. C++ reference - cppreference.com

举报

相关推荐

0 条评论