目录
一、移动构造和移动赋值的特点
二、类成员变量初始化
三、强制生成默认函数的关键字default
四、禁止生成默认函数的关键字delete
五、继承和多态中的fifinal与override关键字
一、移动构造和移动赋值的特点
默认成员函数
原来C++类中,有6个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
最重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
- 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载都没有实现。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载都没有实现,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
namespace mlg
{
class string
{
public:
//构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//交换
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
//拷贝构造函数
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str);
swap(tmp);
}
//移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
//赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
//移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
//析构函数
~string()
{
//delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
class Person
{
public:
//构造函数
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()
{}
private:
mlg::string _name; //姓名
int _age; //年龄
};
int main()
{
Person s1("张三", 21);
Person s2 = std::move(s1); //想要调用Person默认生成的移动构造
return 0;
}
如上面代码所示,如果你的Person类实现了构造函数、析构函数或者拷贝赋值重载,那么执行main函数时,我们想的是调用mlg::string里的移动构造,但是结果显示如下:
如果你将Person类实现了构造函数、析构函数或者拷贝赋值重载全部注释掉,结果显示如下:
两次运行结果不同的原因就在于,移动构造是一个特殊的默认成员函数,s1是一个左值,通过move函数,转变了性质,变成了右值属性,赋值给了Person s2 ,按照正常的逻辑应该回去调用s2的移动构造,然后去调用s1的移动构造;但是当你在Person类实现了构造函数、析构函数或者拷贝赋值重载的时候,编译器就不会生成默认的移动构造函数,就会去调用拷贝构造,进而调用s1的拷贝构造,是一次深拷贝;
如果你想要验证移动赋值,代码如下:
int main() { Person s1("张三", 21); Person s2; s2 = std::move(s1); //想要调用Person默认生成的移动赋值 return 0; }
最后执行的结果和上面的是类似的,通过这样的测试就能明白移动构造和移动赋值相比其他的默认成员函数有什么样的区别。
二、类成员变量初始化
默认生成的构造函数,对于自定义类型的成员会调用其构造函数进行初始化,但并不会对内置类型的成员进行处理。于是C++11支持非静态成员变量在声明时进行初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化。比如:
class Person
{
public:
/*****/
private:
mlg::string _name = "张三"; //姓名
int _age = 20; //年龄
static int _a; //静态成员变量不能给缺省值
};
三、强制生成默认函数的关键字default
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
class Person
{
public:
//拷贝构造函数
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
private:
mlg::string _name; //姓名
int _age; //年龄
};
int main()
{
Person s; //没有合适的默认构造函数可用
return 0;
}
执行上面的代码,编译器会提示Person类没有默认的构造函数,在类和对象章节,我们知道对于构造函数,是这样定义的,我们不写编译器会默认生成一个构造函数,如果写了,则不会生成。Person类中的构造函数虽然是拷贝构造,但它也是构造函数,我们实例化出来的s对象,应该去调用它的默认构造函数,但是编译器没有生成,因为你有了拷贝构造。
出现这样的情况,我们可以自己写,也可以使用default关键字显示指定移动构造生成;
class Person
{
public:
Person() = default; //强制生成默认构造函数
//拷贝构造函数
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
private:
mlg::string _name; //姓名
int _age; //年龄
};
默认成员函数都可以用default关键字强制生成,包括移动构造和移动赋值。
四、禁止生成默认函数的关键字delete
如果要限制某些默认函数的生成:
- 在C++98中,是将该函数设置成private,并且只用声明不用定义,这样当外部调用该函数时就会报错。这样只要其他人想要调用就会报错。
- 在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
Person(const Person& p) = delete;
private:
mlg::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
五、继承和多态中的fifinal与override关键字
在之前多态的博客中,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和fifinal两个关键字,可以帮助用户检测是否重写。
fifinal:
- 修饰类的时候,表示该类不能被继承
- 修饰虚函数的时候,表示该虚函数不能再被重写
class A final //直接限制不能被继承(也称最终类)
{
private:
A(int a = 0)
:_a(a)
{}
protected:
int _a;
};
class B :public A //不能被继承
{
};
/****************************************************/
class c
{
public:
virtual void f() final//限制它不能被子类中的虚函数重写
{
cout << "c::f()" << endl;
}
};
class d :public c
{
public:
virtual void f() //不能被重写
{
cout << "d::f()" << endl;
}
};
override: 检查子类虚函数是否重写了父类某个虚函数,如果没有重写编译报错
class Car
{
public:
virtual void Drive()
{}
};
class Benz :public Car
{
public:
virtual void Drive() override //检查是否完成重写
{ cout << "Benz-舒适" << endl; }
};