最近在考虑做一下CMU15445提升一下工程项目的水平,同时也学习一下现代C++的coding schema与代码style。project的课程网站上有一个推荐的C++新特性的学习项目BootCamp ,简单看了一下写的还是很不错的,从代码加注释的方式解释新特性,而且代码整体也比较贴合现代C++。
这边简单汇总一下这个项目的内容,学习一下,也方便自己后面回顾复习。
项目地址:https://github.com/cmu-db/15445-bootcamp
Reference类 references 就是一个变量的alias
1 2 3 4 5 6 7 void add_three (int &a) { a = a + 3 ; }int a = 10 ;int &b = a;add_three (a);
move_semantics 这个部分比较难理解,首先要明白什么是左值和右值。
简单定义:
左值:objects 指向内存中的一个location
右值:任何不是左值的东西
我自己的理解是右值表示: 这个东西本身
移动一个左值到另外一个左值:
1 2 3 4 5 6 std::vector<int > int_array = {1 , 2 , 3 , 4 }; std::vector<int > stealing_ints = std::move (int_array); std::vector<int > &&rvalue_stealing_ints = std::move (stealing_ints);
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 void move_add_three_and_print (std::vector<int > &&vec) { std::vector<int > vec1 = std::move (vec); vec1.push_back (3 ); for (const int &item : vec1) { std::cout << item << " " ; } std::cout << "\n" ; } void add_three_and_print (std::vector<int > &&vec) { vec.push_back (3 ); for (const int &item : vec) { std::cout << item << " " ; } std::cout << "\n" ; } std::vector<int > int_array2 = {1 , 2 , 3 , 4 }; move_add_three_and_print (std::move (int_array2));std::vector<int > int_array3 = {1 , 2 , 3 , 4 }; add_three_and_print (std::move (int_array3));std::cout << "Printing from int_array3: " << int_array3[1 ] << std::endl;
move constructors 首先引入一个person类,这个类实现了移动构造和移动赋值运算符。并且删除了拷贝构造和拷贝赋值运算符。
所以一旦一个person类被instance化了,这个类不能被拷贝,只能从一个左值移动到另一个左值。
这种删除掉拷贝的东西其实很有用,特别是有时候强制只能存在一个实例。
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 class Person {public : Person () : age_ (0 ), nicknames_ ({}), valid_ (true ) {} Person (uint32_t age, std::vector<std::string> &&nicknames) : age_ (age), nicknames_ (std::move (nicknames)), valid_ (true ) {} Person (Person &&person) : age_ (person.age_), nicknames_ (std::move (person.nicknames_)), valid_ (true ) { std::cout << "Calling the move constructor for class Person.\n" ; person.valid_ = false ; } Person &operator =(Person &&other) { std::cout << "Calling the move assignment operator for class Person.\n" ; age_ = other.age_; nicknames_ = std::move (other.nicknames_); valid_ = true ; other.valid_ = false ; return *this ; } Person (const Person &) = delete ; Person &operator =(const Person &) = delete ; uint32_t GetAge () { return age_; } std::string &GetNicknameAtI (size_t i) { return nicknames_[i]; } void PrintValid () { if (valid_) { std::cout << "Object is valid." << std::endl; } else { std::cout << "Object is invalid." << std::endl; } } private : uint32_t age_; std::vector<std::string> nicknames_; bool valid_; };
对于移动构造的调用也比较重要。
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 int main () { Person andy (15445 , {"andy" , "pavlo" }) ; andy.PrintValid (); Person andy1; andy1 = std::move (andy); andy1.PrintValid (); andy.PrintValid (); Person andy2 (std::move(andy1)) ; andy2.PrintValid (); andy1.PrintValid (); return 0 ; }
模板 templated functions template的关键字,template<class T>
和 template<typename T>
,基本上是一样,具体的区别可以看这个博客
https://mariusbancila.ro/blog/2021/03/15/typename-or-class/
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 template <typename T> T add (T a, T b) { return a + b; }template <typename T, typename U>void print_two_values (T a, U b) { std::cout << a << " and " << b << std::endl; } template <typename T> void print_msg () { std::cout << "Hello world!\n" ; }template <> void print_msg<float >() { std::cout << "print_msg called with float type!\n" ; } template <bool T> int add3 (int a) { if (T) { return a + 3 ; } return a; } std::cout << "Printing add<int>(3, 5): " << add<int >(3 , 5 ) << std::endl; std::cout << "Printing add<float>(2.8, 3.7): " << add<float >(2.8 , 3.7 ) << std::endl; std::cout << "Printing add(3, 5): " << add (3 , 5 ) << std::endl; print_two_values<int , float >(3 , 3.2 ); print_msg<int >(); print_msg<float >(); add3<true >(3 ); add3<false >(3 );
templated classes 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 template <typename T>class Foo { public : Foo (T var) : var_ (var) {} void print () { std::cout << var_ << std::endl; } private : T var_; }; template <typename T, typename U>class Foo2 { public : Foo2 (T var1, U var2) : var1_ (var1) , var2_ (var2) {} void print () { std::cout << var1_ << " and " << var2_ << std::endl; } private : T var1_; U var2_; }; template <typename T>class FooSpecial { public : FooSpecial (T var) : var_ (var) {} void print () { std::cout << var_ << std::endl; } private : T var_; }; template <>class FooSpecial <float > { public : FooSpecial (float var) : var_ (var) {} void print () { std::cout << "hello float! " << var_ << std::endl; } private : float var_; }; template <int T>class Bar { public : Bar () {} void print_int () { std::cout << "print int: " << T << std::endl; } }; Foo<int > a (3 ) ;Foo b (3.4f ) ;Foo2<int , float > c (3 , 3.2f ) ;FooSpecial<int > d (5 ) ;FooSpecial<float > e (4.5 ) ;Bar<150 > f;
Misc wrapper class 包装类用来管理资源,memory file sockets或者network connection。
常用 RAII (Resource Acquisition is Initialization) 技术,这种技术保证了资源的lifetime与它的scope密切结合。当一个包装类构建了,它里面的资源就是可用的,当这个类析构了,资源也就同时不可用。
https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii
https://en.cppreference.com/w/cpp/language/raii
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 class IntPtrManager { public : IntPtrManager () { ptr_ = new int ; *ptr_ = 0 ; } IntPtrManager (int val) { ptr_ = new int ; *ptr_ = val; } ~IntPtrManager () { if (ptr_) { delete ptr_; } } IntPtrManager (IntPtrManager&& other) { ptr_ = other.ptr_; other.ptr_ = nullptr ; } IntPtrManager &operator =(IntPtrManager &&other) { if (ptr_ == other.ptr_) { return *this ; } if (ptr_) { delete ptr_; } ptr_ = other.ptr_; other.ptr_ = nullptr ; return *this ; } IntPtrManager (const IntPtrManager &) = delete ; IntPtrManager &operator =(const IntPtrManager &) = delete ; void SetVal (int val) { *ptr_ = val; } int GetVal () const { return *ptr_; } private : int *ptr_; }; IntPtrManager a (445 ) ;std::cout << "1. Value of a is " << a.GetVal () << std::endl; a.SetVal (645 ); std::cout << "2. Value of a is " << a.GetVal () << std::endl; IntPtrManager b (std::move(a)) ;std::cout << "Value of b is " << b.GetVal () << std::endl;
iterator 迭代器是指向容器内部元素的一种指针。
传统指针也是一种迭代器
1 2 3 4 5 int *array = malloc (sizeof (int ) * 10 );int *iter = array;int zero_elem = *iter;iter++; int first_elem = *iter;
两个关键要素:
取值 * : 返回当前指向元素的值
迭代 ++ (后置): 指向位置+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 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 struct Node { Node (int val) : next_ (nullptr ) , prev_ (nullptr ) , value_ (val) {} Node* next_; Node* prev_; int value_; }; class DLLIterator { public : DLLIterator (Node* head) : curr_ (head) {} DLLIterator& operator ++() { curr_ = curr_->next_; return *this ; } DLLIterator operator ++(int ) { DLLIterator temp = *this ; ++*this ; return temp; } bool operator ==(const DLLIterator &itr) const { return itr.curr_ == this ->curr_; } bool operator !=(const DLLIterator &itr) const { return itr.curr_ != this ->curr_; } int operator *() { return curr_->value_; } private : Node* curr_; }; class DLL { public : DLL () : head_ (nullptr ) , size_ (0 ) {} ~DLL () { Node *current = head_; while (current != nullptr ) { Node *next = current->next_; delete current; current = next; } head_ = nullptr ; } void InsertAtHead (int val) { Node *new_node = new Node (val); new_node->next_ = head_; if (head_ != nullptr ) { head_->prev_ = new_node; } head_ = new_node; size_ += 1 ; } DLLIterator Begin () { return DLLIterator (head_); } DLLIterator End () { return DLLIterator (nullptr ); } Node* head_{nullptr }; size_t size_; }; for (DLLIterator iter = dll.Begin (); iter != dll.End (); ++iter) { std::cout << *iter << " " ; }
namespaces 给标识符提供一个scope。
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 namespace ABC { void spam (int a) { std::cout << "Hello from ABC::spam: " << a << std::endl; } namespace DEF { void bar (float a) { std::cout << "Hello from ABC::DEF::bar: " << a << std::endl; } void uses_bar (float a) { std::cout << "Hello from uses_bar: " ; bar (a); } void uses_spam (int a) { std::cout << "Hello from uses_spam: " ; ABC::spam (a); } } void uses_DEF_bar (float a) { std::cout << "Hello from uses_DEF_bar: " ; DEF::bar (a); } }
using
的用法:
不推荐将命名空间的所有标识符直接导入当前scope
1 2 using namespace B;using C::eggs;
STL 容器 STL: (C++ Standard Library)
vectors 这边只涉及一些基础的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 point_vector.push_back (Point (35 , 36 )); point_vector.emplace_back (37 , 38 ); for (const Point &item : point_vector) { item.PrintPoint (); } int_vector.erase (int_vector.begin () + 2 ); int_vector.erase (int_vector.begin () + 1 , int_vector.end ()); point_vector.erase ( std::remove_if (point_vector.begin (), point_vector.end (), [](const Point &point) { return point.GetX () == 37 ; }), point_vector.end ());
set 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int_set.insert (i); int_set.emplace (i); std::set<int >::iterator search = int_set.find (2 ); if (int_set.count (11 ) == 0 ) { }if (int_set.count (3 ) == 1 ) { }int_set.erase (4 ); int_set.erase (int_set.begin ()); int_set.erase (int_set.find (9 ), int_set.end ()); for (std::set<int >::iterator it = int_set.begin (); it != int_set.end (); ++it) { std::cout << *it << " " ; } for (const int &elem : int_set) { std::cout << elem << " " ; }
unordered_map 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 std::unordered_map<std::string, int > map; map.insert ({"foo" , 2 }); map.insert (std::make_pair ("jignesh" , 445 )); map.insert ({{"spam" , 1 }, {"eggs" , 2 }, {"garlic rice" , 3 }}); map["bacon" ] = 5 ; map["spam" ] = 15 ; std::unordered_map<std::string, int >::iterator result = map.find ("jignesh" ); std::pair<std::string, int > pair = *result; size_t count = map.count ("spam" ); map.erase ("eggs" ); map.erase (map.find ("garlic rice" )); for (std::unordered_map<std::string, int >::iterator it = map.begin (); it != map.end (); ++it) { std::cout << "(" << it->first << ", " << it->second << "), " ; } for (const std::pair<const std::string, int > &elem : map) { std::cout << "(" << elem.first << ", " << elem.second << "), " ; }
auto auto就是自动推断类型的变量,简单的变量类型直接写会比较方便,但是碰到复杂的范型还是用auto增加代码阅读性比较好。
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 template <typename T, typename U> class Abcdefghijklmnopqrstuvwxyz {public : Abcdefghijklmnopqrstuvwxyz (T instance1, U instance2) : instance1_ (instance1), instance2_ (instance2) {} void print () const { std::cout << "(" << instance1_ << "," << instance2_ << ")\n" ; } private : T instance1_; U instance2_; }; template <typename T>Abcdefghijklmnopqrstuvwxyz<T, T> construct_obj (T instance) { return Abcdefghijklmnopqrstuvwxyz<T, T>(instance, instance); } Abcdefghijklmnopqrstuvwxyz<int , int > obj = construct_obj<int >(2 ); auto obj1 = construct_obj<int >(2 );
但是,要注意auto只是类型的自动推断,直接用auto就相当于直接写类型,会发生深拷贝。
1 2 3 4 auto copy_int_values = int_values; auto & ref_int_values = int_values;
常用在循环遍历中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 std::unordered_map<std::string, int > map; map.insert ({{"andy" , 445 }, {"jignesh" , 645 }}); for (std::unordered_map<std::string, int >::iterator it = map.begin (); it != map.end (); ++it) { std::cout << "(" << it->first << "," << it->second << ")" << " " ; } for (auto it = map.begin (); it != map.end (); ++it) {std::cout << "(" << it->first << "," << it->second << ")" << " " ; } for (const auto & elem : vec) { std::cout << elem << " " ; }
STL Memory 主要涉及智能指针,现代c++常用的智能指针主要是 std::unique_ptr
和 std::shared_ptr
unique_ptr 对一个资源的唯一的指针,没有两个unique_ptr实例可以指向同一个object
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 class Point {public : Point () : x_ (0 ), y_ (0 ) {} Point (int x, int y) : x_ (x), y_ (y) {} inline int GetX () { return x_; } inline int GetY () { return y_; } inline void SetX (int x) { x_ = x; } inline void SetY (int y) { y_ = y; } private : int x_; int y_; }; void SetXTo445 (std::unique_ptr<Point> &ptr) { ptr->SetX (445 ); } std::unique_ptr<Point> u1; std::unique_ptr<Point> u2 = std::make_unique<Point>(); std::unique_ptr<Point> u3 = std::make_unique<Point>(2 , 3 ); if (u1) { std::cout << "u1's value of x is " << u1->GetX () << std::endl; } if (u2) { std::cout << "u2's value of x is " << u2->GetX () << std::endl; } 但是可以移动 std::unique_ptr<Point> u4 = std::move (u3);
shared_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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 void modify_ptr_via_ref (std::shared_ptr<Point> &point) { point->SetX (15 ); }void modify_ptr_via_rvalue_ref (std::shared_ptr<Point> &&point) { point->SetY (645 ); } void copy_shared_ptr_in_function (std::shared_ptr<Point> point) { std::cout << "Use count of shared pointer is " << point.use_count () << std::endl; } std::shared_ptr<Point> s1; std::shared_ptr<Point> s2 = std::make_shared<Point>(); std::shared_ptr<Point> s3 = std::make_shared<Point>(2 , 3 ); std::cout << "Pointer s1 is " << (s1 ? "not empty" : "empty" ) << std::endl; std::cout << "Pointer s2 is " << (s2 ? "not empty" : "empty" ) << std::endl; std::cout << "Pointer s3 is " << (s3 ? "not empty" : "empty" ) << std::endl; std::cout << "Number of shared pointer object instances using the data in s3: " << s3.use_count () << std::endl; std::shared_ptr<Point> s4 = s3; std::cout << "Number of shared pointer object instances using the data in s3 " "after one copy: " << s3.use_count () << std::endl; std::shared_ptr<Point> s5 (s4) ;std::cout << "Number of shared pointer object instances using the data in s3 " "after two copies: " << s3.use_count () << std::endl; std::shared_ptr<Point> s6 = std::move (s5); std::cout << "Pointer s5 is " << (s5 ? "not empty" : "empty" ) << std::endl; std::cout << "Number of shared pointer object instances using the data in s3 " "after two copies and a move: " << s3.use_count () << std::endl; modify_ptr_via_ref (s2);modify_ptr_via_rvalue_ref (std::move (s2));copy_shared_ptr_in_function (s2);
STL 同步 synchronization primitive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <mutex> #include <thread> int count = 0 ;std::mutex m; void add_count () { m.lock (); count += 1 ; m.unlock (); } int main () { std::thread t1 (add_count) ; std::thread t2 (add_count) ; t1.join (); t2.join (); std::cout << "Printing count: " << count << std::endl; return 0 ; }
scoped lock std::scoped_lock
是mutex的包装类,提供了RAII形式的获取和释放lock的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int count = 0 ;std::mutex m; void add_count () { std::scoped_lock slk (m) ; count += 1 ; } int main () { std::thread t1 (add_count) ; std::thread t2 (add_count) ; t1.join (); t2.join (); std::cout << "Printing count: " << count << std::endl; return 0 ; }
conditional variable condition variable 允许线程等待一个条件,在满足条件后获取mutex锁。
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 int count = 0 ;std::mutex m; std::condition_variable cv; void add_count_and_notify () { std::scoped_lock slk (m) ; count += 1 ; if (count == 2 ) { cv.notify_one (); } } void waiter_thread () { std::unique_lock lk (m) ; cv.wait (lk, []{return count == 2 ;}); std::cout << "Printing count: " << count << std::endl; } int main () { std::thread t1 (add_count_and_notify) ; std::thread t2 (add_count_and_notify) ; std::thread t3 (waiter_thread) ; t1.join (); t2.join (); t3.join (); return 0 ; }
rwlock c++没有提供reader-writer锁的相关库,但是可以通过 shared_mutex shared_lock unique_lock 来模拟一个
主要解决的是读写者问题,涉及操作系统相关内容。
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 int count = 0 ;std::shared_mutex m; void read_value () { std::shared_lock lk (m) ; std::cout << "Reading value " + std::to_string (count) + "\n" << std::flush; } void write_value () { std::unique_lock lk (m) ; count += 3 ; } int main () { std::thread t1 (read_value) ; std::thread t2 (write_value) ; std::thread t3 (read_value) ; std::thread t4 (read_value) ; std::thread t5 (write_value) ; std::thread t6 (read_value) ; t1.join (); t2.join (); t3.join (); t4.join (); t5.join (); t6.join (); return 0 ; }