在C++11之前一个类有6个默认成员函数,在C++11标准中又新增了两个默认成员函数,分别是移动构造函数和移动赋值函数
默认移动构造和移动赋值生成的条件
也就是说移动构造和移动赋值的生成条件和之前六个默认成员函数不同,并不是不写就会生成默认的。
特别注意,如果我们自己实现了移动构造和移动赋值,就算没有实现拷贝构造和拷贝赋值,先一起也不会生成默认的拷贝构造和拷贝赋值
默认移动构造函数会做什么
验证默认生成的移动构造函数和移动赋值函数所做的工作
要验证默认生成的移动构造和移动赋值所做的工作,这里使用了类一个简化版的string类,其中只编写了几个我们需要的成员函数
//
// 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
#include
#include
#include
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类
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类不会生成默认的移动构造和移动赋值
void clx_person_test1() {
Person s1("clx", 21);
Person s2 = std::move(s1);
}
// 输出
clx_string(const char* str) -- 直接构造
clx_string(const clx_string& s) -- 拷贝构造
clx_string(const char* str) -- 直接构造
可以看到我们已经使用右值取构造s2但是Person类并没有调用默认生成的移动构造函数,因为_name作为自定义类型并没有调用其的移动构造以及移动赋值。这里调用的是Person的拷贝构造函数,其又调用了clx_string类的拷贝构造函数
生成默认移动构造和移动赋值
为了让Person调用默认生成的移动构造函数和移动赋值函数,我们需要将Person类的拷贝构造,拷贝赋值,析构函数都注释掉,再次运行上述代码
clx_string(const char* str) -- 直接构造
clx_string::clx_string(clx_string&& s) -- 移动构造
可以看到Person类默认生成了移动构造函数,其对自己的自定义成员_name调用了自定义成员的移动构造函数_,我们还可以改一下代码看一下默认移动赋值的效果
void clx_person_test1() {
Person s1("clx", 21);
Person s2;
s2 = s1;
}
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支持非静态成员变量在声明时初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化
struct student{
string s = "clx";
int age = 18;
};
void clx_student_test1() {
student s;
cout << s.s << endl; // clx
cout << s.age << endl; // 18
}
注意这里只是声明,是给声明的成员变量一个缺省值。不是定义!不是定义!
C++11可以让我们更好的控制要使用的默认成员函数,假设某些情况我们需要使用某个默认成员函数,但是因为某些原因导致无法生成这个默认成员函数,就可以使用default这个关键字强制其生成
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类中编写了拷贝构造函数,导致无法生成默认的构造函数。默认的构造函数生成条件是没有编写任何类型的构造函数,包括拷贝构造函数
struct student{
student() = default; // 默认生成构造函数
student(const student& stu)
: s(stu.s), age(stu.age){}
string s = "clx";
int age = 18;
};
这样我们可以使用default关键字强制生成默认构造函数。
default不仅能生成默认构造,所有默认成员函数都可以用default关键字强制生成,包括移动构造和移动赋值.
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);
}
clx_string(const char* str) -- 直接构造
clx_string::clx_string(clx_string&& s) -- 移动构造
可以看到默认的移动赋值函数是生成了的
如果我们想要限制某些默认函数生成时,可以通过一下几种方式
final 修饰的类
final修饰的类被称为最终类,最终类无法被继承
class UnInheritable final {}
Final 修饰虚函数
final修饰的虚函数,表示该虚函数不能再被重写,如果字类继承后重写了该虚函数编译就会报错
Override 修饰虚函数
override修饰的字类虚函数,检查该类是否是由父类继承下来的并且必须重写,如果没有重写就会报错