跳转至
  • 左值是什么?右值是什么?两者持续时间?构成:纯右值和将亡值?地址,标识符?特殊的字符串字面量是什么值?
  • 什么是左值引用?左值引用如何绑定?
  • 如何传入左值引用(引用/常量)?返回左值引用的函数,能做什么,如何赋值?
  • 什么运算符返回左值表达式?
  • 函数参数为 const &(常左值引用)可以获取哪些参数?

  • 什么是右值引用?什么运算符返回右值的表达式?

  • 右值生命周期延长,纯右值还是将亡值?
  • 如何给函数传入右值引用?返回右值引用的函数?返回将亡值?
  • 函数参数为常左值引用参数与函数参数为右值引用参数同时存在会如何?

  • move 的作用(cast)?和&&的区别(auto,T)?什么时候万能引用?如何避免万能引用?万能引用有什么问题?

  • 移动构造?移动赋值的实现?如何移动赋值左值?

  • push_back 和 emplace_back 的区别?

  • 完美转发 forward 的作用?
  • 转发引用,引用折叠(左值模板推导,四种引用)?四种场景(两条规则和 typedef 和 decltype(过于晦涩,跳过))?和 auto 是否相同? * 现代 C++之万能引用、完美转发、引用折叠 - 知乎 (zhihu.com) ![[Pasted image 20241104211103.png]]

1. 什么是左值

指既能出现在等号左边也能出现在等号右边的变量(或表达式) - 具有标识符、地址 - 字符串字面量是左值,而且是不可被更改的左值。字符串字面量并不具名,但是可以用&取地址所以也是左值。 返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减 运算符 ,都是返回左值的表达式

located value

int main()
{
//i有存储空间,10没有存储空间
    int i = 10;
// 10 = i;
// 10不能为左值

    //然而左值也可以在右边,因为i是有地址的。
    int a = i;

    //没有存储空间,取的是右值
        int i = GetValue();

    //无法赋值
    //GetValue() = 5;
}

int GetValue()
{
    return 10;

}

2. 函数的左值引用

通过&获得左值引用,左值引用只能绑定左值。

    int intValue1 = 10;    //将intValue1绑定到intValue2和intValue3    
    int &intValue2 = intValue1, &intValue3 = intValue2;     
    intValue2 = 100;    
    std::cout << intValue1 << std::endl;
    std::cout << intValue2 << std::endl;//100    
    std::cout << intValue3 << std::endl;//100
不能将左值引用绑定到一个右值,但是 const 的左值引用可以,常量引用不能修改绑定对象的值

    int &intValue1 = 10;//错误
    const int &intValue2 = 10;//正确
当函数返回的是一个引用,就可以给函数赋值。
int& GetValue()
{
    static int value = 10;
    return value;
}

int main
{
    int i = GetValue();
    GetValue() = 5;

}

3.函数创建

  • 无引用
    void SetValue(int value)
    {
    
    }
    
    int main()
    {
        int i = 10;
        SetValue(i);
        SetValue(10);
    
    }
    
  • 含引用
    void SetValue(int& value)
    {
    
    }
    
    int main()
    {
        int i = 10;
        SetValue(i);
        //不能将右值赋值给左值引用。
        //SetValue(10);
    
    }
    

4. Const + 左值引用

不能将左值引用绑定到一个右值,但是 const 的左值引用可以,常量引用不能修改绑定对象的值

void SetValue(int& value)
{

}

int main()
{
    int i = 10;
    //----Error!!!
    //int& a = 10;

    //一个特殊的规则,const
    //实际上编译器做了一个:
    //int tmp = 10;
    //const int& a = temp;的工作
    const int& a = 10;

}

5. Const 函数左值引用

使用 const 左值引用,参数就可以传入左值、右值了。

void SetValue(const int& value)
{

}

int main()
{
    int i = 10;
    SetValue(i);
    SetValue(10);

}

6. 右值

返回非引用类型/右值引用的函数,连同算术、关系、位以及后置递增/递减运算符,都返回右值的表达式 - 没有地址,标识符 - 临时创建的值: - 10,”Yan“······ - 左值的运算结果+-*/等

void PrintName(std::string& name)
{
    std::cout << name << std::endl;
}
int main()
{
    std::string firstName = "Yan";
    std::string lastName = "Maric";
    std::string fullName = firstName + lastName;

    PrintName(lastName);

    //Error!!!!
    PrintName(firstName + lastName);


}
这就是我们为什么经常看到 c++写的常量引用
void PrintName(const std::string& name)
{
    std::cout << name << std::endl;
}

7.右值引用&&(万能引用)

  • 使得参数只能传进右值
  • 然而,auto&&,T&& 是一个万能引用(既可以是左值,也可以是右值)
    void PrintName(std::string&& name)
    {
        std::cout << name << std::endl;
    }
    int main()
    {
        std::string firstName = "Yan";
        std::string lastName = "Maric";
        std::string fullName = firstName + lastName;
    
        //Error!!!!
        //PrintName(lastName);
    
        PrintName(firstName + lastName);
    
    
    }
    

8. 右值引用重载

如果有常量左值引用,那么右值引用会在引用右值时重载

 void PrintName(const std::string name)
{
    std::cout << "lvalue" << name << std::endl;
}
 void PrintName(std::string&& name)
{
    std::cout << "rvalue" <<name << std::endl;
}
int main()
{
    std::string firstName = "Yan";
    std::string lastName = "Maric";
    std::string fullName = firstName + lastName;

    PrintName(fullName);
    //输出:lvalue + name;
    PrintName(firstName + lastName)
    //输出:rvalue + name:
}

9.万能引用,移动语义 move 与&&

  • 正常情况下,我们想要传入一个对象的值,必须创建一个对象,这个函数不得不获得该对象的所有权,并进行一次拷贝。
  • 如果我们只是想移动对象而不是复制它,那么性能会更高
  • 移动构造:右值引用或 move。复制过来后,删去右值的值并设置为空
  • 移动赋值:先判断是否和现在的数据相同。
    • 相同就直接返回现在的数据
    • 不相同需要 delete 去现在的数据,再通过右值赋值,清理掉右值。 这样就可以实现数据的移动。
    • dest = 作用其实就是 dest.operator=()
  • 移动构造(赋值)一个左值(对象),需要强制转换为右值或者用move(即把一个对象强制转换为临时对象,左值强制转换为右值),此时数据移动到新的对象后,原对象会被设为空指针。这样就实现了对象的移动。
    class String
    {
    public:
        String() = default;
        String(const char* string)
        {
            printf("Created!\n");
            m_Size = strlen(string);
            m_Data = new char[m_size];
            memcpy(m_Data,string,m_Size);
        }
        String(const String& other)
        {
            printf("Created!\n");
            m_Size = strlen(string);
            m_Data = new char[m_Size];
    
        }
    
        ~String()
        {
            delete[] m_Data;
        }
    
        void Print()
        {
            for(uint32_t i = 0; i <m_Size)
        }
    private:
        char* m_Data;
        uint32_t m_Size;
    }
    
    class Entity
    {
    public:
        Entity(const String& name): m_Name(name)
        {
        }
    
        void PrintName()
        {
        }
    private:
        String m_Name;
    }
    

10.完美转发

万能引用

auto&&,T&&等需要推导的值

折叠(写的不好,看前面文章)和 转 发引用

当把一个值右值引用传参后,再把右值引用作为万能引用传进下个函数的参数(模板实现,可以作为右值、左值、常量)

完美转发 forward 和

如果一个本身是 lvalue 的 universal reference 如果绑定在了一个 rvalue 上面,就把它重新转换为 rvalue。函数的名字 (“forward”) 的意思就是,我们希望在传递参数的时候,可以保存参数原来的 lvalueness 或 rvalueness,即是说把参数转发给另一个函数。

上面说了,要传入一个右值或左值,可以使用 const,但是这样没法修改值了

void normal_forwading(const T& t)
{
    show_type(t);
}
那么我们可以使用右值引用(其本身是一个左值):但如下图所示,如果 show_type 想要右值,那么我们该怎么把右值传进去???

template<class T>
void normal_forwading(T &&t)
{
    show_type(t);
}

template<class T>
void show_type(T t)
{
    std::cout << typeid(t).name() << std::endl;

}
但是我们不可能给每个函数都进行一次类型判别,所以我们需要把万能引用传进去。解决方法是,再进行一次万能引用的转换,用于折叠
template<class T>
void normal_forwading(T &&t)
{
    show_type(static_cast<T &&>(t));
}

std 就给了我们这样一个函数

template<class T>
void normal_forwading(T &&t)
{
    show_type(std::forward<T>(t));
}

forward 做了什么?

  • 移除引用
    • 采用模版函数的重载,对左值引用、右值引用、值的传参都进行了特化与重载
  • 然后返回 移除的引用(右值),并对其进行万能引用的强制转换并返回这个引用

11.forward 和 move 的区别

  • move:std:: move 一定会将实参转换为一个右值引用,且不需要指定模版实参,模版实参由函数调用推导出来的。
  • std:: forward 会根据左值和右值进行转发,再使用的时候需要指定实参