【C++】C++11 -- 新功能

文章目录

C++11 -- 新功能

默认成员函数

在C++11之前一个类有6个默认成员函数,在C++11标准中又新增了两个默认成员函数,分别是移动构造函数和移动赋值函数

默认移动构造和移动赋值生成的条件

  • 移动构造函数的生成条件:没有自己实现移动构造函数,并且没有自己实现析构函数,拷贝构造函数和拷贝赋值函数
  • 移动赋值重载函数的生成条件:没有自己实现移动赋值重载函数,并且没有自己实现析构函数,拷贝构造函数,拷贝赋值函数

也就是说移动构造和移动赋值的生成条件和之前六个默认成员函数不同,并不是不写就会生成默认的。

特别注意,如果我们自己实现了移动构造和移动赋值,就算没有实现拷贝构造和拷贝赋值,先一起也不会生成默认的拷贝构造和拷贝赋值

默认移动构造函数会做什么

  • 默认生成的移动构造函数:对于内置类型的成员可以完成值拷贝(浅拷贝),对于自定义类型的成员,如果该成员实现了移动构造就调用它的移动构造,否则就调用它的拷贝构造
  • 默认生成生成的移动赋值函数:对于内置类型的成员会完成值拷贝(浅拷贝),对于自定义类型成员,如果该成员实现了移动赋值就调用它的移动赋值,否则就调用它的拷贝赋值

验证默认生成的移动构造函数和移动赋值函数所做的工作

要验证默认生成的移动构造和移动赋值所做的工作,这里使用了类一个简化版的string类,其中只编写了几个我们需要的成员函数

cpp 复制代码
//
// Created by 陈李鑫 on 2023/7/16.
//
#ifndef SIMULATION_REALIZATION_STL_CLX_STRING_HPP
#define SIMULATION_REALIZATION_STL_CLX_STRING_HPP

#endif //SIMULATION_REALIZATION_STL_CLX_STRING_HPP

#include <iostream>
#include <algorithm>
#include <utility>
#include <cassert>


class clx_string{
public:
    typedef char* iterator;
    iterator begin() { return _str;}
    iterator end() { return _str + _size; }
    const char* c_str() const { return const_cast<const char*>(_str); };
    void swap(clx_string& s);
    clx_string(const char* str = "");
    clx_string(const clx_string& s);
    clx_string(clx_string&& s);
    ~clx_string();
    clx_string& operator=(const clx_string& s);
    clx_string& operator=(clx_string&& s);
    char& operator[](size_t i);
    void reserve(size_t n);
    void push_back(char ch);
    clx_string& operator+=(char ch);
    static clx_string to_string(int value);
private:
    char* _str;
    size_t _size;
    size_t _capacity;
};

void clx_string::swap(clx_string& s) {
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
    std::swap(_str, s._str);
}

clx_string::clx_string(const char* str) {
    std::cout << "clx_string(const char* str) -- 直接构造" << std::endl;
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}

clx_string::clx_string(clx_string&& s)
    :_size(0), _capacity(0), _str(nullptr)
{
    std::cout << "clx_string::clx_string(clx_string&& s) -- 移动构造" << std::endl;
    swap(s);
}

// 拷贝构造函数 以前的写法
//clx_string::clx_string(const clx_string& s) {
//    _size = strlen(s.c_str());
//    _capacity = _size;
//    _str = new char[_capacity + 1];
//    strcpy(_str, s.c_str());
//}

// 拷贝构造函数 现代写法
clx_string::clx_string(const clx_string& s)
    : _str(nullptr), _size(0), _capacity(0)
{
    std::cout << "clx_string(const clx_string& s) -- 拷贝构造"  << std::endl;
    clx_string tmp(s.c_str());
    swap(tmp);
    std::cout << std::endl;
    std::cout << std::endl;
}

clx_string::~clx_string() {
    _size = 0;
    _capacity = 0;
    delete[] _str;
    _str = nullptr;
}
clx_string& clx_string:: operator=(const clx_string& s) {
    std::cout << "clx_string& clx_string:: operator=(const clx_string& s) -- 赋值函数重载" << std::endl;
    clx_string tmp(s.c_str());
    clx_string::swap(tmp);
    std::cout << std::endl;
    std::cout << std::endl;
    return *this;
}

clx_string& clx_string::operator=(clx_string&& s) {
    std::cout << "clx_string& clx_string::operator=(clx_string&& s) -- 移动赋值重载" << std::endl;
    swap(s);
    return *this;
}



char& clx_string::operator[](size_t i) {
    assert(0 <= i && i < _size);
    return _str[i];
}

void clx_string::reserve(size_t n) {
    if (n > _capacity) {
        char* tmp = new char[n + 1];
        strncpy(tmp, _str, _size + 1);
        if (_str) {
            delete[] _str;
        }
        _str = tmp;
        _capacity = n;
    }
}
void clx_string::push_back(char ch) {
    while (_size >= _capacity) {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }
    _str[_size] = ch;
    _str[_size + 1] = 0;
    _size++;
}
clx_string& clx_string::operator+=(char ch) {
    push_back(ch);
    return *this;
}

clx_string clx_string::to_string(int value) {
    clx_string res;
    bool flag = false;
    if (value < 0) {
        flag = true;
        value = -1 * value;
    }
    while (value > 0) {
        char ch = static_cast<char>(value % 10);
        res += ch + '0';
        value /= 10;
    }
    if(flag) res += '-';
    std::reverse(res.begin(), res.end());
    return res;
}

然后再编译一个简单的Person类,Person类中的成员name的类型就是我们模拟实现的string类

cpp 复制代码
class Person{
public:
    explicit Person(const char* name = "", int age = 0)
        :_name(name), _age(age)
    {}
    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;
    }
    
private:
    clx_string _name;   // 姓名
    int _age;           // 年龄
};

虽然Person类当中没有实现移动构造和移动赋值,但是拷贝构造,拷贝赋值,析构函数都实现了,因此Person类不会生成默认的移动构造和移动赋值

cpp 复制代码
void clx_person_test1() {
    Person s1("clx", 21);
    Person s2 = std::move(s1);
}
cpp 复制代码
// 输出
clx_string(const char* str) -- 直接构造
clx_string(const clx_string& s) -- 拷贝构造
clx_string(const char* str) -- 直接构造

可以看到我们已经使用右值取构造s2但是Person类并没有调用默认生成的移动构造函数,因为_name作为自定义类型并没有调用其的移动构造以及移动赋值。这里调用的是Person的拷贝构造函数,其又调用了clx_string类的拷贝构造函数

生成默认移动构造和移动赋值

为了让Person调用默认生成的移动构造函数和移动赋值函数,我们需要将Person类的拷贝构造,拷贝赋值,析构函数都注释掉,再次运行上述代码

cpp 复制代码
clx_string(const char* str) -- 直接构造
clx_string::clx_string(clx_string&& s) -- 移动构造

可以看到Person类默认生成了移动构造函数,其对自己的自定义成员_name调用了自定义成员的移动构造函数_,我们还可以改一下代码看一下默认移动赋值的效果

cpp 复制代码
void clx_person_test1() {
    Person s1("clx", 21);
    Person s2;
    s2 = s1;
}
cpp 复制代码
clx_string(const char* str) -- 直接构造
clx_string(const char* str) -- 直接构造
clx_string& clx_string:: operator=(const clx_string& s) -- 赋值函数重载
clx_string(const char* str) -- 直接构造

类成员变量初始化

默认生成的构造函数,对于其自定义类型的成员会调用其构造函数进行初始化,但并不会对内置类型的成员进行处理。于是对于C++11支持非静态成员变量在声明时初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化

cpp 复制代码
struct student{
    string s = "clx";
    int age = 18;
};

void clx_student_test1() {
    student s;
    cout << s.s << endl;			// clx
    cout << s.age << endl;    // 18
}

注意这里只是声明,是给声明的成员变量一个缺省值。不是定义!不是定义!

强制生成默认函数关键字default

C++11可以让我们更好的控制要使用的默认成员函数,假设某些情况我们需要使用某个默认成员函数,但是因为某些原因导致无法生成这个默认成员函数,就可以使用default这个关键字强制其生成

cpp 复制代码
struct student{
    student(const student& stu)
        : s(stu.s), age(stu.age){}
        
    string s = "clx";
    int age = 18;
};
void clx_student_test2() {
    student s;
}

这样就不行,编译就会报错。因为Person类中编写了拷贝构造函数,导致无法生成默认的构造函数。默认的构造函数生成条件是没有编写任何类型的构造函数,包括拷贝构造函数

cpp 复制代码
struct student{
    student() = default;          // 默认生成构造函数
    student(const student& stu)
        : s(stu.s), age(stu.age){}

    string s = "clx";
    int age = 18;
};

这样我们可以使用default关键字强制生成默认构造函数。

default不仅能生成默认构造,所有默认成员函数都可以用default关键字强制生成,包括移动构造和移动赋值.

cpp 复制代码
class Person{
public:
    explicit Person(const char* name = "", int age = 0)
        :_name(name), _age(age)
    {}
    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(){}
    Person(Person&&) = default;								// 生成默认的移动赋值和拷贝函数
    Person& operator=(Person&&) = default;
private:
    clx_string _name;   // 姓名
    int _age;           // 年龄
};

void clx_person_test1() {
    Person s1("clx", 21);
    Person s2 = move(s1);
}
cpp 复制代码
clx_string(const char* str) -- 直接构造
clx_string::clx_string(clx_string&& s) -- 移动构造

可以看到默认的移动赋值函数是生成了的

禁用生成默认函数的关键字delete

如果我们想要限制某些默认函数生成时,可以通过一下几种方式

  • C++98: 将函数设置成私有,并只声明不实现,这样外部调用该函数就会报错
  • C++11:在该函数的声明后面加上=delete,表示不让编译器生成该函数的默认版本

final and override 关键字

final 修饰的类

final修饰的类被称为最终类,最终类无法被继承

cpp 复制代码
class UnInheritable final {}

Final 修饰虚函数

final修饰的虚函数,表示该虚函数不能再被重写,如果字类继承后重写了该虚函数编译就会报错

Override 修饰虚函数

override修饰的字类虚函数,检查该类是否是由父类继承下来的并且必须重写,如果没有重写就会报错

相关推荐
苹果酱056719 分钟前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
2401_8470565526 分钟前
Altium Designer脚本工具定制
网络·数据库
qmx_0728 分钟前
MFC-基础架构
c++·mfc
万象.29 分钟前
STL_vector实现及干货以及vector迭代器失效问题
c++
想变成自大狂31 分钟前
C++中的异构容器
开发语言·c++
小米里的大麦34 分钟前
【C++】深入理解引用:从基础到进阶详解
c++·经验分享·笔记·引用·引用和指针
神仙别闹37 分钟前
基于Python+SQLite的课程管理系统
数据库·sqlite
掐指一算乀缺钱39 分钟前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
晚睡早起₍˄·͈༝·͈˄*₎◞ ̑̑44 分钟前
苍穹外卖学习笔记(七)
java·windows·笔记·学习·mybatis
就这个java爽!1 小时前
JAVA网络编程【基于TCP和UDP协议】超详细!!!
java·开发语言·网络·tcp/ip·udp·eclipse·idea