当前位置: 首页 > news >正文

【C++】31.C++11​(3)

文章目录

  • 5. 新的类功能
    • 5.1 默认的移动构造和移动赋值
    • 5.2 成员变量声明时给缺省值
    • 5.3 defult和delete
    • 5.4 final与override
  • 6. 可变参数模板
    • 6.1 基本语法及原理
    • 6.2 包扩展
    • 6.3 empalce系列接口
        • list实现empalce


5. 新的类功能

5.1 默认的移动构造和移动赋值

  • 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个用处不大,默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
class Person
{
public:
    // 构造函数
    // const char* name = "" 默认参数为空字符串
    // int age = 0 默认参数为0
    // 初始化列表初始化成员变量_name和_age
    Person(const char* name = "", int age = 0)
        :_name(name)
        , _age(age)
    {}

    // 拷贝构造函数
    // 参数为const引用,避免修改传入对象
    // 初始化列表完成深拷贝
    /*Person(const Person& p)
        :_name(p._name)
        ,_age(p._age)
    {}*/

    // 赋值运算符重载
    // 返回引用以支持连续赋值
    // 检查自赋值
    // 深拷贝所有成员
    /*Person& operator=(const Person& p)
    {
        if(this != &p)
        {
            _name = p._name;
            _age = p._age;
        }
        return *this;
    }*/

    // 析构函数
    // 成员变量的析构函数会自动调用
    /*~Person()
    {}*/

private:
    bit::string _name;  // 姓名,string类型
    int _age;          // 年龄,整型
};

int main()
{
    Person s1;                  // 调用构造函数,默认构造
    Person s2 = s1;            // 调用拷贝构造(编译器生成的默认版本)
    Person s3 = std::move(s1); // 调用移动构造(编译器生成的默认版本)
    Person s4;                 // 调用构造函数,默认构造
    s4 = std::move(s2);       // 调用移动赋值运算符(编译器生成的默认版本)
    return 0;
}

5.2 成员变量声明时给缺省值

成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表用这个却绳子初始化,这个在类和对象部分讲过了,忘了就去复习。


5.3 defult和delete

  • C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
  • 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:
    // 构造函数
    // const char* name = "" 默认参数为空字符串
    // int age = 0 默认参数为0
    // 使用参数构造bit::string对象_name,直接赋值给_age
    Person(const char* name = "", int age = 0)
        :_name(name)
        , _age(age)
    {}

    // 拷贝构造函数
    // 对传入对象p进行深拷贝
    // 会调用bit::string的拷贝构造函数完成_name的深拷贝
    // _age直接赋值拷贝
    Person(const Person& p)
        :_name(p._name)
        ,_age(p._age)
    {}

    // 移动构造函数
    // = default表示使用编译器生成的默认版本
    // 会调用bit::string的移动构造函数,转移_name的资源所有权
    // _age直接按位拷贝
    Person(Person&& p) = default;
    
    // 如果打开这个注释,会禁用拷贝构造
    // 禁用后,对象将不能进行拷贝构造,只能进行移动构造
    //Person(const Person& p) = delete;

private:
    bit::string _name;  // 姓名
    int _age;          // 年龄
};

int main()
{
    Person s1;                  // 调用默认构造函数
    Person s2 = s1;            // 调用拷贝构造函数,深拷贝s1
    Person s3 = std::move(s1); // 调用移动构造函数,转移s1的资源
                               // 转移后s1的_name应该被置空
    return 0;
}

5.4 final与override

这个在继承和多态章节已经进行了详细讲过了,忘了就去复习。


6. 可变参数模板

6.1 基本语法及原理

  • C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包:表示零或多个函数参数。
  • template <class ...Args> void Func(Args... args) {}
  • template <class ...Args> void Func(Args&... args) {}
  • template <class ...Args> void Func(Args&&... args) {}
  • 我们用省略号来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class...typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟…指出接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。
  • 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
  • 这里我们可以使用sizeof...运算符去计算参数包中参数的个数。
// 可变参数模板函数
// Args&&... 是参数包,可以接受任意数量、任意类型的参数
// Args&&是万能引用,可以接受左值和右值
template <class ...Args>
void Print(Args&&... args)
{
    // sizeof...(args)返回参数包中参数的个数
    cout << sizeof...(args) << endl;
}

int main()
{
    double x = 2.2;
    Print();                              // 参数包为空
    Print(1);                             // 参数包含一个右值1
    Print(1, string("xxxxx"));            // 参数包含右值1和右值string
    Print(1.1, string("xxxxx"), x);       // 参数包含右值1.1、右值string和左值x
    return 0;
}

// 原理1:编译器会根据实际调用展开成以下具体函数
// 根据引用折叠规则:
// - 右值传入时,T&&折叠为右值引用
// - 左值传入时,T&&折叠为左值引用
void Print();                                                 // 对应Print()
void Print(int&& arg1);                                      // 对应Print(1)
void Print(int&& arg1, string&& arg2);                       // 对应Print(1, string("xxxxx"))
void Print(double&& arg1, string&& arg2, double& arg3);      // 对应Print(1.1, string("xxxxx"), x)

// 原理2:如果没有可变参数模板,就需要手动写很多重载版本
// 为了支持不同数量的参数,需要写很多函数模板
void Print();

template <class T1>
void Print(T1&& arg1);

template <class T2, class T2>
void Print(T1&& arg1, T2&& arg2);

template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);
// ... 需要写更多重载来支持更多参数

// 可变参数模板的优势:
// 1. 一个模板就能处理任意数量参数
// 2. 通过参数包和引用折叠,实现了完美转发
// 3. 大大减少了代码量

6.2 包扩展

  • 对于一个参数包,我们除了能计算他的参数个数,我们能做的唯一的事情就是扩展它,当扩展一个包时,我们还要提供用于每个扩展元素的模式,扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放一个省略号(…)来触发扩展操作。底层的实现细节如图1所示。
  • C++还支持更复杂的包扩展,直接将参数包依次展开依次作为实参给一个函数去处理。

19a214249011e9f1802ffe2624d1ff70

代码1:

// ================== 不可行的实现方式 ==================
template <class ...Args>
void Print(Args... args)
{
    // 这种实现方式是错误的,原因:
    // 1. 可变参数模板是在编译时展开的
    // 2. 无法在运行时通过下标访问参数包
    // 3. args不是一个数组,不支持下标访问
    cout << sizeof...(args) << endl;  // sizeof...是编译时操作符
    for (size_t i = 0; i < sizeof...(args); i++)
    {
        cout << args[i] << " ";   // 错误:参数包不支持下标访问
    }
    cout << endl;
}

// ================== 正确的递归实现 ==================
// 递归终止函数
// 当参数包为空时的特化版本
void ShowList()
{
    cout << endl;  // 仅打印换行
}

// 递归展开函数
template <class T, class ...Args>
void ShowList(T x, Args... args)
{
    cout << x << " ";       // 处理第一个参数
    ShowList(args...);      // 递归处理剩余参数
    // args... 是参数包展开,将剩余参数传递给下一次递归调用
}

// 包装函数,提供更简洁的接口
template <class ...Args>
void Print(Args... args)
{
    ShowList(args...);   // 转发所有参数到ShowList
}

int main()
{
    // 测试各种参数数量的调用
    Print();                             // 调用空参数版本
    Print(1);                            // 调用一个参数版本
    Print(1, string("xxxxx"));           // 调用两个参数版本
    Print(1, string("xxxxx"), 2.2);      // 调用三个参数版本
    return 0;
}

// ================== 编译器展开示例 ==================
// 对于 Print(1, string("xxxxx"), 2.2) 的调用
// 编译器会将模板展开为以下具体函数:

// 第一层展开
void ShowList(int x, string y, double z)
{
    cout << x << " ";      // 打印1
    ShowList(y, z);        // 继续递归
}

// 第二层展开
void ShowList(string x, double z)
{
    cout << x << " ";      // 打印"xxxxx"
    ShowList(z);           // 继续递归
}

// 第三层展开
void ShowList(double x)
{
    cout << x << " ";      // 打印2.2
    ShowList();            // 调用终止函数
}

// 最终调用基础版本
void ShowList()
{
    cout << endl;          // 打印换行,结束递归
}

// Print函数相应展开为:
void Print(int x, string y, double z)
{
    ShowList(x, y, z);     // 调用展开后的ShowList
}

代码2:

// 特化版本:处理无参数的情况
void CppPrint()
{
    cout << endl;  // 只打印换行
}

// 辅助函数:打印单个参数并返回0
// 返回0是为了配合数组初始化方式展开参数包
template <class T>
int PrintArg(T t)
{
    cout << t << " ";  // 打印参数
    return 0;          // 返回0用于数组初始化
}

// 可变参数模板函数
// 使用数组初始化的方式展开参数包
template <class ...Args>
void CppPrint(Args... args)
{
    // 这行代码完成了参数包的展开
    // PrintArg(args)... 会展开为 PrintArg(arg1), PrintArg(arg2), ...
    // 使用数组初始化列表,确保按顺序展开和执行
    int a[] = { PrintArg(args)... };
    cout << endl;  // 最后打印换行
}

int main()
{
    // 测试不同数量参数的调用
    CppPrint();                              // 调用无参数版本
    CppPrint(1);                             // 调用一个参数版本
    CppPrint(1, 2);                          // 调用两个参数版本
    CppPrint(1, 2, 2.2);                     // 调用三个参数版本
    CppPrint(1, 2, 2.2, string("xxxx"));     // 调用四个参数版本

    return 0;
}

代码3:

// Date类定义
class Date
{
public:
    // 构造函数,使用默认参数
    // 支持传入0-3个参数的构造
    Date(int year = 1, int month = 1, int day = 1)
        :_year(year)
        , _month(month)
        , _day(day)
    {
        cout << "Date构造" << endl;
    }

    // 拷贝构造函数
    // 用于通过已有Date对象构造新对象
    Date(const Date& d)
        :_year(d._year)
        , _month(d._month)
        , _day(d._day)
    {
        cout << "Date拷贝构造" << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

// 可变参数模板工厂函数
// 用于创建Date对象,可接收0-3个参数
template <class ...Args>
Date* Create(Args... args)
{
    // 将参数包完美转发给Date的构造函数
    Date* ret = new Date(args...);
    return ret;
}

int main()
{
    // 测试不同参数数量的对象创建
    Date* p1 = Create();              // 调用无参构造:Date(1, 1, 1)
    Date* p2 = Create(2023);          // 调用单参构造:Date(2023, 1, 1)
    Date* p3 = Create(2023, 9);       // 调用双参构造:Date(2023, 9, 1)
    Date* p4 = Create(2023, 9, 27);   // 调用三参构造:Date(2023, 9, 27)

    Date d(2023, 1, 1);               // 创建一个Date对象
    Date* p5 = Create(d);             // 通过拷贝构造创建新对象

    return 0;
}

6.3 empalce系列接口

  • template <class... Args> void emplace_back (Args&&... args);
  • template <class... Args> iterator emplace (const_iterator position, Args&&... args);
  • C++11以后STL容器新增了empalce系列的接口,empalce系列的接口均为模板可变参数,功能上兼容pushinsert系列,但是empalce还支持新玩法,假设容器为container<T>empalce还支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。
  • emplace_back总体而言是更高效,推荐以后使用emplace系列替代insertpush系列
  • 第二个程序中我们模拟实现了listemplaceemplace_back接口,这里把参数包不段往下传递,最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前面说的empalce支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。
  • 传递参数包过程中,如果是 Args&&... args 的参数包,要用完美转发参数包,方式如下std::forward<Args>(args)... ,否则编译时包扩展后右值引用变量表达式就变成了左值。

代码1:

#include<list>

int main()
{
    list<bit::string> lt;

    // 1.传入左值的情况
    bit::string s1("111111111111");
    lt.emplace_back(s1);  // 会调用拷贝构造,和push_back效果一样
    cout << "*********************************" << endl;

    // 2.传入右值的情况
    lt.emplace_back(move(s1));  // 会调用移动构造,和push_back效果一样
    cout << "*********************************" << endl;

    // 3.直接传入构造string需要的参数
    lt.emplace_back("111111111111");  // 直接在list节点空间构造string对象
                                      // 避免了先构造临时对象再拷贝/移动的过程
                                      // 这是push_back做不到的
    cout << "*********************************" << endl;

    // 4.对于复杂类型(pair)的测试
    list<pair<bit::string, int>> lt1;

    // 4.1 传入左值pair
    pair<bit::string, int> kv("苹果", 1);
    lt1.emplace_back(kv);  // 会调用pair的拷贝构造,和push_back效果一样
    cout << "*********************************" << endl;

    // 4.2 传入右值pair
    lt1.emplace_back(move(kv));  // 会调用pair的移动构造,和push_back效果一样
    cout << "*********************************" << endl;

    // 4.3 直接传入构造pair需要的参数
    lt1.emplace_back("苹果", 1);  // 直接在list节点空间构造pair对象
                                  // 避免了先构造临时pair再拷贝/移动的过程
                                  // 这是push_back做不到的
    cout << "*********************************" << endl;

    return 0;
}

list实现empalce

List.h

namespace bit
{
    // 链表节点结构
    template<class T>
    struct ListNode
    {
        ListNode<T>* _next;
        ListNode<T>* _prev;
        T _data;

        // 移动构造版本的节点构造函数
        ListNode(T&& data)
            :_next(nullptr)
            , _prev(nullptr)
            , _data(move(data))  // 使用移动语义构造数据
        {}

        // 完美转发构造函数,支持任意参数包构造数据成员
        template <class... Args>
        ListNode(Args&&... args)
            : _next(nullptr)
            , _prev(nullptr)
            , _data(std::forward<Args>(args)...)  // 完美转发参数包到数据成员的构造函数
        {}
    };

    // 迭代器类,支持不同类型的引用和指针
    template<class T, class Ref, class Ptr>
    struct ListIterator
    {
        typedef ListNode<T> Node;
        typedef ListIterator<T, Ref, Ptr> Self;
        Node* _node;

        ListIterator(Node* node)
            :_node(node)
        {}

        // 前置++运算符重载
        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }

        // 前置--运算符重载
        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }

        // 解引用运算符重载
        Ref operator*()
        {
            return _node->_data;
        }

        // 不等运算符重载
        bool operator!=(const Self& it)
        {
            return _node != it._node;
        }
    };

    // list类的实现
    template<class T>
    class list
    {
        typedef ListNode<T> Node;
        public:
            typedef ListIterator<T, T&, T*> iterator;
            typedef ListIterator<T, const T&, const T*> const_iterator;

            // 返回指向第一个节点的迭代器
            iterator begin()
            {
                return iterator(_head->_next);
            }

            // 返回指向哨兵节点的迭代器
            iterator end()
            {
                return iterator(_head);
            }

            // 初始化空链表(创建哨兵节点)
            void empty_init()
            {
                _head = new Node();  // 创建哨兵节点
                _head->_next = _head;  // 首尾相连
                _head->_prev = _head;
            }

            // 默认构造函数
            list()
            {
                empty_init();
            }

            // 尾插左值
            void push_back(const T& x)
            {
                insert(end(), x);
            }

            // 尾插右值
            void push_back(T&& x)
            {
                insert(end(), move(x));
            }

            // 在指定位置插入左值
            iterator insert(iterator pos, const T& x)
            {
                Node* cur = pos._node;
                Node* newnode = new Node(x);
                Node* prev = cur->_prev;
                // 连接节点
                prev->_next = newnode;
                newnode->_prev = prev;
                newnode->_next = cur;
                cur->_prev = newnode;
                return iterator(newnode);
            }

            // 在指定位置插入右值
            iterator insert(iterator pos, T&& x)
            {
                Node* cur = pos._node;
                Node* newnode = new Node(move(x));
                Node* prev = cur->_prev;
                // 连接节点
                prev->_next = newnode;
                newnode->_prev = prev;
                newnode->_next = cur;
                cur->_prev = newnode;
                return iterator(newnode);
            }

            // emplace_back的可变参数模板版本
            template <class... Args>
            void emplace_back(Args&&... args)
            {
                insert(end(), std::forward<Args>(args)...);
            }

            // 在指定位置直接构造插入的可变参数模板版本
            template <class... Args>
            iterator insert(iterator pos, Args&&... args)
            {
                Node* cur = pos._node;
                // 直接在节点位置构造对象
                Node* newnode = new Node(std::forward<Args>(args)...);
                Node* prev = cur->_prev;
                // 连接节点
                prev->_next = newnode;
                newnode->_prev = prev;
                newnode->_next = cur;
                cur->_prev = newnode;
                return iterator(newnode);
            }

        private:
            Node* _head;  // 指向哨兵节点的指针
    };
}

Test.cpp

#include"List.h"

int main()
{
    bit::list<bit::string> lt;

    // 测试1:传入左值
    bit::string s1("111111111111");
    lt.emplace_back(s1);   // 参数是左值,会调用拷贝构造
                          // 这种情况下emplace_back和push_back性能一样
    cout << "*********************************" << endl;

    // 测试2:传入右值
    lt.emplace_back(move(s1));  // 参数是右值,会调用移动构造
                                // 这种情况下emplace_back和push_back性能一样
    cout << "*********************************" << endl;

    // 测试3:直接传入构造string需要的参数
    lt.emplace_back("111111111111");  // 直接在节点空间构造string对象
                                      // 避免了先构造临时string对象,再拷贝/移动的过程
                                      // 这是push_back做不到的,更高效
    cout << "*********************************" << endl;

    // 测试4:对复杂类型pair的操作
    bit::list<pair<bit::string, int>> lt1;

    // 测试4.1:传入左值pair
    pair<bit::string, int> kv("苹果", 1);
    lt1.emplace_back(kv);  // 会调用pair的拷贝构造
                          // 和push_back效果一样
    cout << "*********************************" << endl;

    // 测试4.2:传入右值pair
    lt1.emplace_back(move(kv));  // 会调用pair的移动构造
                                 // 和push_back效果一样
    cout << "*********************************" << endl;

    // 测试4.3:直接传入构造pair需要的参数
    lt1.emplace_back("苹果", 1);  // 直接在节点空间构造pair对象
                                  // 避免了先构造临时pair再拷贝/移动
                                  // 这是push_back做不到的,更高效
    cout << "*********************************" << endl;

    return 0;
}

相关文章:

  • ShenNiusModularity项目源码学习(8:数据库操作)
  • unity学习40:导入模型的 Animations文件夹内容,动画属性和修改动画文件
  • C# Task 学习记录
  • 3、树莓派5 安装VNC查看器 开启VNC服务器
  • linu软件编程——IO
  • 2月15日星期六今日早报简报微语报早读
  • React - 高阶函数-函数柯里化
  • 2015-2024年上市公司商道融绿esg评级数据
  • 寒假刷题Day24
  • Word正文中每两个字符之间插入一个英文半角空格
  • 服务器虚拟化(详解)
  • 枚举Enum用法
  • ros:ur机械臂初识
  • 基于STM32的智能垃圾分类回收系统
  • 【kafka系列】At Most Once语义
  • matlab-simulink
  • 鲸鱼算法优化Transformer+KAN网络并应用于时序预测任务
  • TrueNAS in Hyper-V
  • React:初识React
  • 脉冲当量含义
  • 14岁女生瞒报年龄文身后洗不掉,法院判店铺承担六成责任
  • 浙江一民企拍地后遭政府两次违约,“民告官”三年又提起民事诉讼
  • 重温经典|《南郭先生》:不模仿别人,不重复自己
  • 人民日报整版聚焦:铭记二战历史,传承深厚友谊
  • 上海发布预付卡消费“10点提示”:警惕“甩锅闭店”套路
  • 第1现场 | 印巴冲突:印50多年来首次举行大规模民防演习