Operators
在C++中可以重载运算符,实现自定义的运算。当原有运算符被重载后,只有当输入参数满足条件才会执行。
全局模板函数中重载运算符
1 2 3 4 5
| template<typename T> T operator+(const T& lhs,const T& rhs) { return a+b; }
|
类模板中重载运算符
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 31
| template<typename T> class MyClass { public: MyClass(T v,T u):x(v),y(u){} MyClass operator+(const MyClass& other) const { return MyClass(x+other.x,y+other.y); } friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) { os<<obj.x<<" "<<obj.y; return os; } MyClass& operator=(const MyClass& other) { if(this==&other)return *this; x=other.x; y=other.y; return *this; } private: T x,y; }
|
注意在模板类中重载时,[],(),->,=
必须重载为成员函数,<<
必须重载为非成员函数,即友元函数。
对于操作符左右两边地位相等的操作,最好重载为友元函数,如+,<
,对于地位不等的,如 +=
,重载为成员函数。
对于要改变左边的值的操作,如 +=,=
,在重载时需使用如下规定:
1 2 3 4
| T& operator +=(const T& a) { ... }
|
即需要返回引用,这样支持链式操作。
重载STL相关操作
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 Person { public: Person(string name, int age) : name(name), age(age) {}
bool operator<(const Person& other) const { return age < other.age; }
private: std::string name; int age; };
int main() {
set<Person> peopleSet = {...};
map<Person, std::string> peopleMap = {...};
return 0; }
|
POLA原则
即 $Principle\ of\ Least\ Astonishment (POLA)$ ,设计要符合常识。
Something about CLASS
Special member functions
1 2 3 4 5 6 7 8 9 10
| class MyClass { public: MyClass() = default; ~MyClass() = default; MyClass(const MyClass& other) = default; MyClass& operator=(const MyClass& other) = default; MyClass(MyClass&& other) = default; MyClass& operator=(MyClass&& other) = default; };
|
注意默认拷贝构造函数、运算符一般是浅拷贝(STL某些事深拷贝)。实现自己的功能,可以自定义特殊函数:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| class MyClass { public: MyClass() : number(0), ptr(new int(0)), text(new char[1]) { text[0] = '\0'; std::cout << "Default constructor called\n"; }
~MyClass() { delete ptr; delete[] text; std::cout << "Destructor called\n"; }
MyClass(const MyClass& other) : number(other.number), ptr(new int(*other.ptr)), text(new char[std::strlen(other.text) + 1]) { std::strcpy(text, other.text); std::cout << "Copy constructor called\n"; }
MyClass& operator=(const MyClass& other) { std::cout << "Copy assignment operator called\n";
if (this == &other)return *this; number = other.number; delete ptr; delete[] text;
ptr = new int(*other.ptr); text = new char[std::strlen(other.text) + 1]; std::strcpy(text, other.text);
return *this; }
MyClass(MyClass&& other) noexcept: number(std::move(other.number)), ptr(std::move(other.ptr)), text(std::move(other.text)) { std::cout << "Move constructor called\n"; other.ptr = nullptr; other.text = nullptr; }
MyClass& operator=(MyClass&& other) noexcept { std::cout << "Move assignment operator called\n";
if (this == &other) return *this;
delete ptr; delete[] text;
ptr = std::move(other.ptr); text = std::move(other.text); number = std::move(other.number);
other.ptr = nullptr; other.text = nullptr; other.number = 0;
return *this; }
private: int number; int* ptr; char* text; };
int main() { MyClass obj1;
MyClass obj2(obj1);
MyClass obj3; obj3 = obj1;
MyClass obj4(std::move(obj1));
MyClass obj5; obj5 = std::move(obj2); return 0; }
|
3 principles
$Rule\ of\ 0$:在设计类时尽量避免自定义任何特殊成员函数(不需要管理任何资源)。
$Rule\ of\ 3$:需要同时定义析构函数、拷贝构造函数、拷贝赋值运算符。
$Rule\ of\ 5$:需要同时定义析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符。
Inheritance
基础用法:
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
| class Base { public: int public_value; protected: int protected_value; private: int private_value; };
class DerivedPublic : public Base { };
class DerivedProtected : protected Base { };
class DerivedPrivate : private Base { };
|
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
| class Base { public: Base() { std::cout << "Base constructor called" << std::endl; } ~Base() { std::cout << "Base destructor called" << std::endl; } };
class Derived : public Base { public: Derived() { std::cout << "Derived constructor called" << std::endl; } ~Derived() { std::cout << "Derived destructor called" << std::endl; } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Base { public: Base(int x) { std::cout << "Base constructor called with " << x << std::endl; } };
class Derived : public Base { public: Derived(int x) : Base(x) { std::cout << "Derived constructor called" << std::endl; } };
|
另外,若一个类含有纯虚函数,那么这个类就叫做抽象类,自身不能实例化,它的子类创建时必须实现这个功能。同时也有非纯虚函数。
1 2 3 4 5 6 7 8 9 10 11
| class Base { public: virtual void show() = 0; };
class Derived : public Base { public: void show() override { std::cout << "Derived show()" << std::endl; } };
|
L-value and R-value
Basic
值分为左值与右值,同时还存在一类值叫将亡值($xvalue$),同属于两类值。
左值为可以取地址的值,如:
1 2 3 4
| int a = 3; int* p = &a; *p; const int b = 2;
|
右值为不可取地址的值,如字面常量,表达式返回值等:
1 2 3
| 10; x + y; fmin(x, y);
|
注意的是,++a
为左值,a++
为右值。
对于引用语法,无论左值引用还是右值引用,都是给对象取别名。
1 2 3 4 5 6 7 8 9
| int val = 2; int* ptr = 0x02248837; vector<int> v1{1, 2, 3};
auto& ptr2 = ptr; auto&& v4 = v1 + v2; auto& ptr3 = &val; auto&& val2 = val; const auto& ptr3 = ptr + 5;
|
特别的,对于 const 左值引用
可以引用右值。右值被引用后,如 v4
,虽然它是右值引用变量,但是它也是左值,可以对 v4
取地址与赋值操作。
std::move
std::move(x)
强制把 x
变为右值引用,但是本身并不移动任何东西,这个操作只是为了调用对象的移动构造函数或者移动赋值运算符,具体清理被调用的对象过程在移动构造函数或移动赋值运算符中实现。
右值引用可以接受左值的 std::move(x)
。
应用:比如需要写一个泛型的交换函数:
1 2 3 4 5 6 7
| template<typename T> void my_swap(T& a,T& b) { T temp=std::move(a); a=std::move(b); b=std::move(temp); }
|
Application
左值在应用中有着实际意义。传值传参和传值返回都会产生拷贝,有的甚至是深拷贝,代价很大。而左值引用的实际意义在于做参数和做返回值都可以减少拷贝,从而提高效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void func1(string s) {...}
void func2(const string& s) {...}
int main() { string s1("Hello World!"); func1(s1); func2(s1); return 0; }
|
但是缺陷在于对于在函数体执行结束后就会被析构的对象,就无法返回引用。而右值引用的出现弥补了这一短板,编译器在编译时会自动将返回值优化为移动构造。移动赋值。
另外对于大型对象,如果存在移动处理,那么使用 std::move()
,就会很有用,将拷贝变成了移动操作,减少了空间消耗。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class MyClass { public: MyClass(MyClass&& other) noexcept: number(std::move(other.number)), ptr(std::move(other.ptr)), text(std::move(other.text)) { std::cout << "Move constructor called\n"; other.ptr = nullptr; other.text = nullptr; }
MyClass& operator=(MyClass&& other) noexcept { std::cout << "Move assignment operator called\n";
if (this == &other) return *this;
delete ptr; delete[] text;
ptr = std::move(other.ptr); text = std::move(other.text); number = std::move(other.number);
other.ptr = nullptr; other.text = nullptr; other.number = 0;
return *this; } };
int main() { MyClass obj1,obj2; MyClass obj3(std::move(obj1));
MyClass obj4 = std::move(obj2); return 0; }
|
移动构造函数、移动赋值运算符与拷贝构造函数、拷贝赋值运算符最大的区别就是,前者不用拷贝,直接移动,对于大型对象的处理很有用。但是在使用移动构造时要保证之后不再使用被移动的对象。
Perfect forwarding
对于确定类型的 &&
表示右值引用,但是对于不确定类型的 T&&
就表示 万能引用 ,模板类型必须通过推断才能确定,其接收左值后会被推导为左值引用,接收右值后会被推导为右值引用。
完美转发 是指在函数模板中,完全依照模板的参数类型,将参数传递给当前函数模板中的另外一个函数。然而根据以上知识,右值引用仍是左值,所以无法完美转发。为了实现这个功能,要用到 std::forward
来传递参数类型。
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
| void Func(int& x) { cout << "左值引用" << endl; }
void Func(const int& x) { cout << "const左值引用" << endl; }
void Func(int&& x) { cout << "右值引用" << endl; }
void Func(const int&& x) { cout << "const右值引用" << endl; }
template<typename T> void PerfectForward(T&& t) { Func(std::forward<T>(t)); }
int main() { int a = 4; PerfectForward(a);
const int b = 8; PerfectForward(b);
PerfectForward(10);
const int c = 13; PerfectForward(std::move(c));
return 0; }
|
完美转发主要应用于提升模板函数的泛化能力,创建通用工厂函数,提高代码的灵活性和可维护性,减少拷贝和重复构造等,在库函数和泛型编程中应用很多。
RAII and Smarter Pointers
RAII
RAII是一种思想,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
Smarter Pointers
所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。C++11的智能指针包括三类:unique_ptr,shared_ptr,weak_ptr
。
在于 new
了一个对象后,需要自己 delete
才能释放资源,若不 delete
是不会自动调用析构函数的。而如果忘记了 delete
,就有可能导致内存泄漏。而智能指针则能自动释放。
unique_ptr
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
| unique_ptr<Test> t1;
unique_ptr<Test> t2(new Test);
unique_ptr<int[]> t3;
unique_ptr<int[]> t4(new int[5]);
unique_ptr<Test> t7(new Test); unique_ptr<Test> t8(new Test); t7 = std::move(t8);
vector<unique_ptr<string>> vec; unique_ptr<string> p3(new string("I'm P3")); unique_ptr<string> p4(new string("I'm P4"));
vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); vec[0] = std::move(vec[1]);
unique_ptr<Test>t; Test new_t=t.release(); t.reset(new Test);
|
缺点:不能多个智能指针托管同一个指针,由于排他性会产生问题。
shared_ptr
原理是可以记录特定内存对象的智能指针数量,复制或拷贝时引用次数+1,析构是引用次数-1。
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
| shared_ptr<Person> sp1; shared_ptr<Person> sp2(new Person(2));
cout << "sp1 use_count() = " << sp1.use_count() << endl; cout << "sp2 use_count() = " << sp2.use_count() << endl << endl;
shared_ptr<int> up1(new int(10)); shared_ptr<int> up2(up1);
shared_ptr<int> up3 = make_shared<int>(2); shared_ptr<string> up4 = make_shared<string>("字符串"); shared_ptr<Person> up5 = make_shared<Person>(9);
shared_ptrr<int> up1(new int(10)); shared_ptr<int> up2(new int(11)); up1 = up2;
std::swap(p1,p2); p1.swap(p2);
|
缺点:两个类各持有对方的管控成员,会造成 count
不为零0,从而不能调用析构函数。
weak_ptr
weak_ptr
设计的目的是为配合 shared_ptr
而引入的一种智能指针来协助 shared_ptr
工作, 它只可以从一个 shared_ptr
或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr
没有重载 *
和 ->
但可以使用 lock
获得一个可用的 shared_pt
r 对象,用完后记得置 null
。