- 右值引用(&&)与移动构造函数
- 类特殊成员函数与delete/default
- using 与 typedef 的区别
- union的使用
- typeid与type_info类
右值引用(&&)与移动构造函数
看下面一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class A { public: A() : _num(new int(33)) { std::cout << "A()" << std::endl; }
~A() noexcept { std::cout << "~A()" << std::endl; delete _num; }
private: int* _num; };
int main() { using namespace std;
{ A a; }
return 0; }
|
运行后先后在命令行中输出“A()”和“~A()”。这是最一般的调用,没有问题。但当调用类A的构造函数语句改成下面形式时,看输出结果:
1 2 3 4 5 6 7
| { vector<A> vec; vec.push_back(A()); }
|
如果按照上面的代码其实是无法运行的,看结果知道调用了两次析构函数,然后在析构函数里面delete
了两次,这很明显会报错,因此这个输出结果是把析构函数中的delete
语句删掉后得到的。这也说明一个问题,当使用一个容器来存储一个未命名的类实例时,调用了一次构造函数,但总体调用了两次析构函数,这很明显不是我们想看到的。
那么首先,导致这个问题的原因是,vec.push_back(A());
首先创建了一个未命名实例,接着将该实例送入容器中,送入的过程调用了拷贝构造函数,这个临时实例在拷贝构造函数执行之后就结束了生命周期,因此被销毁了,此时调用了一次析构函数;当容器被销毁时,容器中保存的实例再次调用了一次析构函数。最终调用了两次析构函数。
要解决这种临时实例的拷贝问题,C++11中有一个特别的方法,就是依赖移动构造函数,移动构造函数的参数为类类型的右值引用,表明该参数是一个右值,即将被销毁,因此可以在这个地方对右值的状态进行一次清理,以免造成不必要的提前销毁。使用移动构造函数优化上述例子的代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class A { public: A() : _num(new int(33)) {}
A(A&& other) noexcept { _num = other._num; other._num = nullptr; }
~A() noexcept { if (_num) { delete _num; } }
private: int* _num; };
|
有了移动构造函数,在对临时实例进行拷贝时,优先调用移动构造函数,若不存在才调用拷贝构造函数。临时实例使用完之后会自动销毁,为了避免销毁不必要的资源需要对状态进行清理,而拷贝构造函数的参数是const
常量,无法修改数据,因此只能在移动构造函数中进行修改,这就是其存在的必要性,也是右值引用一个很典型的应用场景。
类特殊成员函数与delete/default
创建一个类,如果不做任何生命,默认带有以下四种函数,称作类特殊成员函数
- 默认构造函数
- 拷贝构造函数
- 析构函数
- 拷贝赋值运算符
保证了一个类的实例具有默认行为(创建、拷贝赋值、销毁),举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class MyClass {};
int main() { MyClass c1; MyClass c2(c1); MyClass c3 = c1; MyClass c4; c4 = c1; return 0; }
|
四个函数的函数参数如下所示:
1 2 3 4 5 6 7
| class MyClass { public: MyClass() {} MyClass(const MyClass&) {} MyClass& operator=(const MyClass&) {} ~MyClass() {} };
|
但有时候用不到这四个函数中的某一个(比如拷贝赋值),那么此时可以使用C++11中的一个新特性:将该函数赋值为delete
关键字:
1 2 3 4
| class MyClass { public: MyClass& operator=(const MyClass&) = delete; };
|
此时,再调用相关功能,IDE就会报错:
1 2 3 4 5 6
| int main() { MyClass c1, c2; c1 = c2; return 0; }
|
也可以将其赋值为default
,和不写没啥区别:
1 2 3 4
| class MyClass { public: MyClass& operator=(const MyClass&) = default; };
|
using 与 typedef 的区别
在类型定义中的使用没有区别:
1 2 3 4 5
| using uchar = unsigned char; typedef unsigned char uchar;
using int_vector = std::vector<int>; typedef std::vector<int> int_vector;
|
但需要使用模板别名时,只能使用using
:
1 2 3 4 5 6 7 8 9 10 11
| template<typename T> class ClassA { public: ClassA() { std::cout << typeid(T).name() << std::endl; } };
template<typename T> using B = ClassA<T>;
template<typename T> typedef ClassA<int> C;
|
union的使用
union是C语言的关键字,用法和结构体struct一样,区别在于:结构体中存放了多少变量,该结构体所占内存为所有变量字节数的总和;union中也可以存放多个变量,但所有变量共用一个内存地址(起点位置相同),且该union的大小为长度最大的那个变量所占的字节数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| struct block1 { int a; float b; double c; };
union block2 { int a; float b; double c; };
int main() { block1 b1; block2 b2;
int s1 = sizeof(b1); int s2 = sizeof(b2);
void* ptr; ptr = &b1.a; ptr = &b1.b; ptr = &b1.c; ptr = &b2.a; ptr = &b2.b; ptr = &b2.c;
return 0; }
|
typeid与type_info类
typeid
是C++的关键字,用法同sizeof
:
包含一个参数,返回值是type_info
类。可以通过如下方式获取数据类型的输出字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MyClass {};
int main() { std::cout << typeid(int).name() << std::endl; std::cout << typeid(double).name() << std::endl; std::cout << typeid(MyClass).name() << std::endl; std::cout << typeid(typeid(MyClass)).name() << std::endl; std::cout << typeid(int*).name() << std::endl; std::cout << typeid(MyClass*).name() << std::endl;
return 0; }
|