diff --git a/.README.md.un~ b/.README.md.un~ new file mode 100644 index 00000000..8759b194 Binary files /dev/null and b/.README.md.un~ differ diff --git a/README.md b/README.md index fd1cf187..de7c339c 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,6 @@ --- -#### 拓展部分: - -- [C++中如何将string类型转换为int类型?](./basic_content/extent/string_int.md) ### 2.进阶部分 @@ -131,12 +128,30 @@ for(decl:col) { #### 3.1 [极客时间《现代C++实战30讲》](https://time.geekbang.org/channel/home) -- [堆、栈、RAII:C++里该如何管理资源?](./morden_C++_30) - - [堆](./morden_C++_30/RAII/heap.cpp) - - [栈](./morden_C++_30/RAII/stack.cpp) - - [RAII](./morden_C++_30/RAII/RAII.cpp) +- [堆、栈、RAII:C++里该如何管理资源?](./modern_C++_30) + - [堆](./modern_++_30/RAII/heap.cpp) + - [栈](./modern_C++_30/RAII/stack.cpp) + - [RAII](./modern_C++_30/RAII/RAII.cpp) + + + +### 4.拓展部分 + +#### 4.1 [C++惯用法](./codingStyleIdioms) + +##### 你最喜欢的c++编程风格惯用法是什么? + +- [1.类初始化列表](./codingStyleIdioms/1_classInitializers) +- [2.枚举类替换命名空间](./codingStyleIdioms/2_enumclass_namespace) +- [3.RAII(资源获取即初始化)](./codingStyleIdioms/3_RAII) +- [4.copy and swap](./codingStyleIdioms/4_copy-swap) +- [5.pImpl(指针指向具体实现)](./codingStyleIdioms/5_pImpl) + +#### 4.2 一些问题 + +- [C++中如何将string类型转换为int类型?](./basic_content/extent/string_int.md) -### 4.代码运行 +### 5.代码运行 - **代码环境** @@ -146,7 +161,7 @@ Ubuntu 18.04 CLion gcc/g++ -### 5.关于作者: +### 6.关于作者: 个人公众号: diff --git a/README.md~ b/README.md~ new file mode 100644 index 00000000..de7c339c --- /dev/null +++ b/README.md~ @@ -0,0 +1,169 @@ +# C++那些事 + +### 0.项目概要 + +学习C++内容,包括理论、源码、实践、课程代码、项目等 + +### 1.基础部分 + +- [const那些事](./basic_content/const) +- [static那些事](./basic_content/static) +- [this那些事](./basic_content/this) +- [inline那些事](./basic_content/inline) +- [sizeof那些事](./basic_content/sizeof) +- [函数指针那些事](./basic_content/func_pointer) +- [纯虚函数和抽象类那些事](./basic_content/abstract) +- [vptr_vtable那些事](./basic_content/vptr_vtable) +- [virtual那些事](./basic_content/virtual) +- [volatile那些事](./basic_content/volatile) +- [assert那些事](./basic_content/assert) +- [位域那些事](./basic_content/bit) +- [extern那些事](./basic_content/extern) +- [struct那些事](./basic_content/struct) +- [struct与class那些事](./basic_content/struct_class) +- [union那些事](./basic_content/union) +- [c实现c++多态那些事](./basic_content/c_poly) +- [explicit那些事](./basic_content/explicit) +- [friend那些事](./basic_content/friend) +- [using那些事](./basic_content/using) +- [::那些事](./basic_content/::) +- [enum那些事](./basic_content/enum) +- [decltype那些事](./basic_content/decltype) +- [引用与指针那些事](./basic_content/pointer_refer) +- [宏那些事](./basic_content/macro) + +--- + + +### 2.进阶部分 + +#### 2.1 [effective_c++](./effective_c++) + +正在更新... + +#### 2.2 [C++2.0新特性](./c++2.0/) + +- [Variadic Templates](./c++2.0/variadic) +- Spaces in Template Expressions + +```cpp +vector > //ok in each C++ version +vector> // before c++ 11 error error: ‘>>’ should be ‘> >’ within a nested template argument list,c++11后可以正常通过 +``` + +- [nullptr and nullptr_t](./c++2.0/nullptr.cpp) +- [Automatic Type Deduction with auto](./c++2.0/auto.cpp) +- [Uniform Initialization ](./c++2.0/uniform_initialization.cpp) +- [initializer_list](./c++2.0/initializer.cpp) +- [explicit for ctors taking more than one argument](./c++2.0/explicit.cpp) +- [range-based for statement](./c++2.0/auto.cpp) + +```cpp +for(decl:col) { + statement +} +``` + +- [=default,=delete](./c++2.0/default_delete.cpp) + + 如果你自行定义了一个ctor,那么编译器就不会给你一个default ctor + 如果强制加上=default,就可以重新获得并使用default ctor. + +- Alias(化名)Template(template typedef) + +[alias.cpp](./c++2.0/alias.cpp) + +[template_template.cpp](./c++2.0/template_template.cpp) + +- [template template parameter](./c++2.0/template_template.cpp) +- [type alias](./c++2.0/type_alias.cpp) +- [noexcept](./c++2.0/noexcept.cpp) +- [override](./c++2.0/override.cpp) +- [final](./c++2.0/final.cpp) +- [decltype](./c++2.0/decltype.cpp) +- [lambda](./c++2.0/lambda.cpp) +- [Rvalue reference](./c++2.0/rvalue.cpp) +- [move aware class](./c++2.0/move.cpp) +- 容器-结构与分类 + +(1) 序列式容器包括:array(C++2.0新引入),vector,deque,list,forward_list(C++2.0新引入) + +(2) 关联式容器包括:set/multiset,map/multimap + +(3) 无序容器(C++2.0新引入,更换原先hash_xxx为unordered_xxx)包括:unordered_map/unordered_multimap,unordered_set/unordered_multiset + +- [Hash Function](./c++2.0/hash.cpp) +- [tuple](./c++2.0/tuple.cpp) + +学习资料:https://www.bilibili.com/video/av51863195?from=search&seid=3610634846288253061 + +#### 2.3 [C++并发编程v1](./c++2.0/./concurrency_v1) + +- [第一章](./c++2.0/./concurrency_v1/chapter1) +- [第二章](./c++2.0/./concurrency_v1/chapter2) + +学习资料:https://chenxiaowei.gitbook.io/cpp_concurrency_in_action/ + +#### 2.4 [STL源码剖析](./stl_src) + +**stl源码剖析:gcc4.9.1** + +- [array](./stl_src/array.md) +- [deque](./stl_src/deque.md) +- [queue and stack](./stl_src/queue_stack.md) +- [list](./stl_src/list.md) +- [vector](./stl_src/vector.md) +- [typename](./stl_src/typename.md) +- [traits](./stl_src/traits.md) +- [iterator](./stl_src/iterator.md) +- [谈谈STL设计之EBO优化](./stl_src/谈谈STL设计之EBO优化.md) +- [rb_tree](./stl_src/rb_tree.md) +- [set and multiset](set_multiset.md) +- [map and multimap](./stl_src/map_multimap.md) +- [hashtable](./stl_src/hashtable.md) +- [myhashtable](./stl_src/myhashtable.md) +- [unordered_map](./stl_src/unordered_map.md) + +### 3.学习课程 + +#### 3.1 [极客时间《现代C++实战30讲》](https://time.geekbang.org/channel/home) + +- [堆、栈、RAII:C++里该如何管理资源?](./modern_C++_30) + - [堆](./modern_++_30/RAII/heap.cpp) + - [栈](./modern_C++_30/RAII/stack.cpp) + - [RAII](./modern_C++_30/RAII/RAII.cpp) + + + +### 4.拓展部分 + +#### 4.1 [C++惯用法](./codingStyleIdioms) + +##### 你最喜欢的c++编程风格惯用法是什么? + +- [1.类初始化列表](./codingStyleIdioms/1_classInitializers) +- [2.枚举类替换命名空间](./codingStyleIdioms/2_enumclass_namespace) +- [3.RAII(资源获取即初始化)](./codingStyleIdioms/3_RAII) +- [4.copy and swap](./codingStyleIdioms/4_copy-swap) +- [5.pImpl(指针指向具体实现)](./codingStyleIdioms/5_pImpl) + +#### 4.2 一些问题 + +- [C++中如何将string类型转换为int类型?](./basic_content/extent/string_int.md) + +### 5.代码运行 + +- **代码环境** + +Ubuntu 18.04 + +- **工具** + +CLion gcc/g++ + +### 6.关于作者: + +个人公众号: + +![](./img/wechat.jpg) + diff --git a/codingStyleIdioms/.README.md.swp b/codingStyleIdioms/.README.md.swp new file mode 100644 index 00000000..00e7e63c Binary files /dev/null and b/codingStyleIdioms/.README.md.swp differ diff --git "a/codingStyleIdioms/1_classInitializers/1.1_\347\261\273\344\271\213\351\227\264\345\265\214\345\245\227.cpp" "b/codingStyleIdioms/1_classInitializers/1.1_\347\261\273\344\271\213\351\227\264\345\265\214\345\245\227.cpp" new file mode 100644 index 00000000..2693939c --- /dev/null +++ "b/codingStyleIdioms/1_classInitializers/1.1_\347\261\273\344\271\213\351\227\264\345\265\214\345\245\227.cpp" @@ -0,0 +1,52 @@ +// +// Created by light on 19-12-9. +// + +#include + +class Animal { +public: + Animal() { + std::cout << "Animal() is called" << std::endl; + } + + Animal(const Animal &) { + std::cout << "Animal (const Animal &) is called" << std::endl; + } + + Animal &operator=(const Animal &) { + std::cout << "Animal & operator=(const Animal &) is called" << std::endl; + return *this; + } + + ~Animal() { + std::cout << "~Animal() is called" << std::endl; + } +}; + +class Dog { +public: + // 第一种: 使用初始化列表。 + Dog(const Animal &animal) : __animal(animal) { + std::cout << "Dog(const Animal &animal) is called" << std::endl; + } + // 第二种:构造函数赋值来初始化对象。 +// Dog(const Animal &animal) { +// __animal = animal; +// std::cout << "Dog(const Animal &animal) is called" << std::endl; +// } + ~Dog() { + std::cout << "~Dog() is called" << std::endl; + } + +private: + Animal __animal; +}; + +int main() { + Animal animal; + std::cout << std::endl; + Dog d(animal); + std::cout << std::endl; + return 0; +} \ No newline at end of file diff --git "a/codingStyleIdioms/1_classInitializers/1.2_\346\227\240\351\273\230\350\256\244\346\236\204\351\200\240\347\232\204\347\273\247\346\211\277.cpp" "b/codingStyleIdioms/1_classInitializers/1.2_\346\227\240\351\273\230\350\256\244\346\236\204\351\200\240\347\232\204\347\273\247\346\211\277.cpp" new file mode 100644 index 00000000..15e27bf8 --- /dev/null +++ "b/codingStyleIdioms/1_classInitializers/1.2_\346\227\240\351\273\230\350\256\244\346\236\204\351\200\240\347\232\204\347\273\247\346\211\277.cpp" @@ -0,0 +1,43 @@ +// +// Created by light on 19-12-9. +// +#include +class Animal { +public: + Animal(int age) { + std::cout << "Animal(int age) is called" << std::endl; + } + + Animal(const Animal & animal) { + std::cout << "Animal (const Animal &) is called" << std::endl; + } + + Animal &operator=(const Animal & amimal) { + std::cout << "Animal & operator=(const Animal &) is called" << std::endl; + return *this; + } + + ~Animal() { + std::cout << "~Animal() is called" << std::endl; + } + +}; + +class Dog : Animal { +public: + Dog(int age) : Animal(age) { + std::cout << "Dog(int age) is called" << std::endl; + } + + ~Dog() { + std::cout << "~Dog() is called" << std::endl; + } +}; + +int main() { + Animal animal(10); + std::cout << std::endl; + Dog d(100); + std::cout << std::endl; + return 0; +} \ No newline at end of file diff --git "a/codingStyleIdioms/1_classInitializers/1.3_\347\261\273\344\270\255const\346\225\260\346\215\256\346\210\220\345\221\230\343\200\201\345\274\225\347\224\250\346\225\260\346\215\256\346\210\220\345\221\230.cpp" "b/codingStyleIdioms/1_classInitializers/1.3_\347\261\273\344\270\255const\346\225\260\346\215\256\346\210\220\345\221\230\343\200\201\345\274\225\347\224\250\346\225\260\346\215\256\346\210\220\345\221\230.cpp" new file mode 100644 index 00000000..5e221c45 --- /dev/null +++ "b/codingStyleIdioms/1_classInitializers/1.3_\347\261\273\344\270\255const\346\225\260\346\215\256\346\210\220\345\221\230\343\200\201\345\274\225\347\224\250\346\225\260\346\215\256\346\210\220\345\221\230.cpp" @@ -0,0 +1,20 @@ +// +// Created by light on 19-12-9. +// + +#include + +class Animal { +public: + Animal(int age,std::string name):age_(age),name_(name) { + std::cout << "Animal(int age) is called" << std::endl; + } +private: + int &age_; + const std::string name_; +}; + +int main() { + Animal animal(10,"hh"); + return 0; +} \ No newline at end of file diff --git a/codingStyleIdioms/1_classInitializers/README.md b/codingStyleIdioms/1_classInitializers/README.md new file mode 100644 index 00000000..e6bf1d0d --- /dev/null +++ b/codingStyleIdioms/1_classInitializers/README.md @@ -0,0 +1,211 @@ +# 初始化列表与赋值 + +- const成员的初始化只能在构造函数初始化列表中进行 +- 引用成员的初始化也只能在构造函数初始化列表中进行 +- 对象成员(对象成员所对应的类没有默认构造函数)的初始化,也只能在构造函数初始化列表中进行 + +## 类之间嵌套 + +**第一种: 使用初始化列表。** + + +```cpp +class Animal { +public: + Animal() { + std::cout << "Animal() is called" << std::endl; + } + + Animal(const Animal &) { + std::cout << "Animal (const Animal &) is called" << std::endl; + } + + Animal &operator=(const Animal &) { + std::cout << "Animal & operator=(const Animal &) is called" << std::endl; + return *this; + } + + ~Animal() { + std::cout << "~Animal() is called" << std::endl; + } +}; + +class Dog { +public: + Dog(const Animal &animal) : __animal(animal) { + std::cout << "Dog(const Animal &animal) is called" << std::endl; + } + + ~Dog() { + std::cout << "~Dog() is called" << std::endl; + } + +private: + Animal __animal; +}; + +int main() { + Animal animal; + std::cout << std::endl; + Dog d(animal); + std::cout << std::endl; + return 0; +} +``` + +运行结果: +```cpp +Animal() is called + +Animal (const Animal &) is called +Dog(const Animal &animal) is called + +~Dog() is called +~Animal() is called +~Animal() is called +``` + +依次分析从上到下: + +main函数中`Animal animal;`调用默认构造。 + +`Dog d(animal);`等价于: + +``` +Animal __animal = animal; +``` +实际上就是调用了拷贝构造,因此输出了: +``` +Animal (const Animal &) is called +``` +再然后打印Dog的构造函数里面的输出。 + +最后调用析构,程序结束。 + +**第二种:构造函数赋值来初始化对象。** + +构造函数修改如下: +```cpp +Dog(const Animal &animal) { + __animal = animal; + std::cout << "Dog(const Animal &animal) is called" << std::endl; +} +``` +此时输出结果: +``` +Animal() is called + +Animal() is called +Animal & operator=(const Animal &) is called +Dog(const Animal &animal) is called + +~Dog() is called +~Animal() is called +~Animal() is called +``` + +于是得出: + +当调用`Dog d(animal);`时,等价于: + +先定义对象,再进行赋值,因此先调用了默认构造,再调用=操作符重载函数。 + +```cpp +// 假设之前已经有了animal对象 +Animal __animal; +__animal = animal; +``` + +> 小结 + +通过上述我们得出如下结论: + +- **类中包含其他自定义的class或者struct,采用初始化列表,实际上就是创建对象同时并初始化** +- **而采用类中赋值方式,等价于先定义对象,再进行赋值,一般会先调用默认构造,在调用=操作符重载函数。** + +## 无默认构造函数的继承关系中 + +现考虑把上述的关系改为继承,并修改Animal与Dog的构造函数,如下代码: + +```cpp +class Animal { +public: + Animal(int age) { + std::cout << "Animal(int age) is called" << std::endl; + } + + Animal(const Animal & animal) { + std::cout << "Animal (const Animal &) is called" << std::endl; + } + + Animal &operator=(const Animal & amimal) { + std::cout << "Animal & operator=(const Animal &) is called" << std::endl; + return *this; + } + + ~Animal() { + std::cout << "~Animal() is called" << std::endl; + } +}; + +class Dog : Animal { +public: + Dog(int age) : Animal(age) { + std::cout << "Dog(int age) is called" << std::endl; + } + + ~Dog() { + std::cout << "~Dog() is called" << std::endl; + } + +}; +``` + +上述是通过初始化列表给基类带参构造传递参数,如果不通过初始化列表传递,会发生什么影响? + +去掉初始化列表 +``` +Dog(int age) { + std::cout << "Dog(int age) is called" << std::endl; +} +``` +运行程序: + +``` +error: no matching function for call to ‘Animal::Animal()’ +``` + +由于在Animal中没有默认构造函数,所以报错,遇到这种问题属于灾难性的,我们应该尽量避免,可以通过初始化列表给基类的构造初始化。 + + +## 类中const数据成员、引用数据成员 + +特别是引用数据成员,必须用初始化列表初始化,而不能通过赋值初始化! + +例如:在上述的Animal中添加私有成员,并修改构造函数: +```cpp +class Animal { +public: + Animal(int age,std::string name) { + std::cout << "Animal(int age) is called" << std::endl; + } +private: + int &age_; + const std::string name_; +}; +``` + +报下面错误: + +```cpp +error: uninitialized reference member in ‘int&’ +``` + +应该改为下面: + +```cpp +Animal(int age, std::string name) : age_(age), name_(name) { + std::cout << "Animal(int age) is called" << std::endl; +} +``` + diff --git a/codingStyleIdioms/1_classInitializers/initializer.cpp b/codingStyleIdioms/1_classInitializers/initializer.cpp new file mode 100644 index 00000000..8bd8bfc8 --- /dev/null +++ b/codingStyleIdioms/1_classInitializers/initializer.cpp @@ -0,0 +1,23 @@ +// +// Created by light on 19-12-9. +// + +#include + +using namespace std; + +class A { +public: + A(int a) : _a(a), _p(nullptr) { // 初始化列表 + + } + +private: + int _a; + int *_p; +}; + +int main() { + A aa(10); + return 0; +} diff --git a/codingStyleIdioms/2_enumclass/README.md b/codingStyleIdioms/2_enumclass/README.md new file mode 100644 index 00000000..fba98ce0 --- /dev/null +++ b/codingStyleIdioms/2_enumclass/README.md @@ -0,0 +1,71 @@ +# C++惯用法之enum class + +在Effective modern C++中Item 10: Prefer scoped enums to unscoped enum,调到要用有范围的enum class代替无范围的enum。 + +例如: + +```cpp +enum Shape {circle,retangle}; +auto circle = 10; // error +``` + +上述错误是因为两个circle在同一范围。 +对于enum等价于: +```cpp +#define circle 0 +#define retangle 1 +``` +因此后面再去定义circle就会出错。 + +所以不管枚举名是否一样,里面的成员只要有一致的,就会出问题。 +例如: +```cpp +enum A {a,b}; +enum B {c,a}; +``` +a出现两次,在enum B的a处报错。 + +根据前面我们知道,enum名在范围方面没有什么作用,因此我们想到了namespace,如下例子: +```cpp +// 在创建枚举时,将它们放在名称空间中,以便可以使用有意义的名称访问它们: +namespace EntityType { + enum Enum { + Ground = 0, + Human, + Aerial, + Total + }; +} + +void foo(EntityType::Enum entityType) +{ + if (entityType == EntityType::Ground) { + /*code*/ + } +} +``` +将命名空间起的有意思点,就可以达到想要的效果。 + +但是不断的使用命名空间,势必太繁琐,而且如果我不想使用namespace,要达到这样的效果,便会变得不安全,也没有约束。 + +因此在c++11后,引入enum class。 + +enum class 解决了为enum成员定义类型、类型安全、约束等问题。 +回到上述例子: +```cpp +// enum class +enum class EntityType { + Ground = 0, + Human, + Aerial, + Total +}; + +void foo(EntityType entityType) +{ + if (entityType == EntityType::Ground) { + /*code*/ + } +} +``` +这便是这一节要阐述的惯用法:enum class。 \ No newline at end of file diff --git a/codingStyleIdioms/2_enumclass/namespace.cpp b/codingStyleIdioms/2_enumclass/namespace.cpp new file mode 100644 index 00000000..4fc822e2 --- /dev/null +++ b/codingStyleIdioms/2_enumclass/namespace.cpp @@ -0,0 +1,43 @@ +// +// Created by light on 19-12-9. +// + +#include + +using namespace std; +// 在创建枚举时,将它们放在名称空间中,以便可以使用有意义的名称访问它们: +namespace EntityType { + enum Enum { + Ground = 0, + Human, + Aerial, + Total + }; +} + +void foo(EntityType::Enum entityType) +{ + if (entityType == EntityType::Ground) { + /*code*/ + } +} + +// enum class +enum class EntityType1 { + Ground = 0, + Human, + Aerial, + Total +}; + +void foo(EntityType1 entityType) +{ + if (entityType == EntityType1::Ground) { + /*code*/ + } +} + +int main() { + + return 0; +} diff --git a/codingStyleIdioms/3_RAII/RAII.cpp b/codingStyleIdioms/3_RAII/RAII.cpp new file mode 100644 index 00000000..6a54bc57 --- /dev/null +++ b/codingStyleIdioms/3_RAII/RAII.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +using namespace std; +// RAII 资源获取即初始化,例1 + +enum class shape_type { + circle, + triangle, + rectangle, +}; + + +class shape { +public: + shape() { cout << "shape" << endl; } + + virtual void print() { + cout << "I am shape" << endl; + } + + virtual ~shape() {} +}; + +class circle : public shape { +public: + circle() { cout << "circle" << endl; } + + void print() { + cout << "I am circle" << endl; + } +}; + +class triangle : public shape { +public: + triangle() { cout << "triangle" << endl; } + + void print() { + cout << "I am triangle" << endl; + } +}; + +class rectangle : public shape { +public: + rectangle() { cout << "rectangle" << endl; } + + void print() { + cout << "I am rectangle" << endl; + } +}; + +// 利用多态 上转 如果返回值为shape,会存在对象切片问题。 +shape *create_shape(shape_type type) { + switch (type) { + case shape_type::circle: + return new circle(); + case shape_type::triangle: + return new triangle(); + case shape_type::rectangle: + return new rectangle(); + } +} + +class shape_wrapper { +public: + explicit shape_wrapper(shape *ptr = nullptr) : ptr_(ptr) {} + + ~shape_wrapper() { + delete ptr_; + } + + shape *get() const { + return ptr_; + } + +private: + shape *ptr_; +}; + + +void foo() { + shape_wrapper ptr(create_shape(shape_type::circle)); + ptr.get()->print(); +} + +int main() { + + // 第一种方式 + shape *sp = create_shape(shape_type::circle); + sp->print(); + delete sp; + + // 第二种方式 RAII + foo(); + + return 0; +} diff --git a/codingStyleIdioms/3_RAII/RAII.md b/codingStyleIdioms/3_RAII/RAII.md new file mode 100644 index 00000000..f12e7945 --- /dev/null +++ b/codingStyleIdioms/3_RAII/RAII.md @@ -0,0 +1,312 @@ +# C++惯用法之消除垃圾收集器-资源获取即初始化方法(RAII) + +## 0.导语 + +在C语言中,有三种类型的内存分配:静态、自动和动态。静态变量是嵌入在源文件中的常数,因为它们有已知的大小并且从不改变,所以它们并不那么有趣。自动分配可以被认为是堆栈分配——当一个词法块进入时分配空间,当该块退出时释放空间。它最重要的特征与此直接相关。在C99之前,自动分配的变量需要在编译时知道它们的大小。这意味着任何字符串、列表、映射以及从这些派生的任何结构都必须存在于堆中的动态内存中。 + +程序员使用四个基本操作明确地分配和释放动态内存:malloc、realloc、calloc和free。前两个不执行任何初始化,内存可能包含碎片。除了自由,他们都可能失败。在这种情况下,它们返回一个空指针,其访问是未定义的行为;在最好的情况下,你的程序会崩溃。在最坏的情况下,你的程序看起来会工作一段时间,在崩溃前处理垃圾数据。 + +例如: + +```cpp +int main() { + char *str = (char *) malloc(7); + strcpy(str, "toptal"); + printf("char array = \"%s\" @ %u\n", str, str); + + str = (char *) realloc(str, 11); + strcat(str, ".com"); + printf("char array = \"%s\" @ %u\n", str, str); + + free(str); + + return(0); +} +``` + +输出: + +```cpp +char array = "toptal" @ 2762894960 +char array = "toptal.com" @ 2762894960 +``` + +尽管代码很简单,但它已经包含了一个反模式和一个有问题的决定。在现实生活中,你不应该直接写字节数,而应该使用sizeof函数。类似地,我们将char *数组精确地分配给我们需要的字符串大小的两倍(比字符串长度多一倍,以说明空终止),这是一个相当昂贵的操作。一个更复杂的程序可能会构建一个更大的字符串缓冲区,允许字符串大小增长。 + +## 1.RAII的发明:新希望 + +至少可以说,所有手动管理都是令人不快的。 在80年代中期,Bjarne Stroustrup为他的全新语言C ++发明了一种新的范例。 他将其称为“资源获取就是初始化”,其基本见解如下:**可以指定对象具有构造函数和析构函数,这些构造函数和析构函数在适当的时候由编译器自动调用,这为管理给定对象的内存提供了更为方便的方法。** 需要,并且该技术对于不是内存的资源也很有用。 + +意味着上面的例子在c++中更简洁: + +```cpp +int main() { + std::string str = std::string ("toptal"); + std::cout << "string object: " << str << " @ " << &str << "\n"; + + str += ".com"; + std::cout << "string object: " << str << " @ " << &str << "\n"; + + return(0); +} +``` + +输出: + +```cpp +string object: toptal @ 0x7fffa67b9400 +string object: toptal.com @ 0x7fffa67b9400 +``` + +在上述例子中,我们没有手动内存管理!构造string对象,调用重载方法,并在函数退出时自动销毁。不幸的是,同样的简单也会导致其他问题。让我们详细地看一个例子: + +```cpp +vector read_lines_from_file(string &file_name) { + vector lines; + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines.push_back(line); + } + + file_handle.close(); + + return lines; +} + +int main(int argc, char* argv[]) { + // get file name from the first argument + string file_name (argv[1]); + int count = read_lines_from_file(file_name).size(); + cout << "File " << file_name << " contains " << count << " lines."; + + return 0; +} +``` + +输出: + +```cpp +File makefile contains 38 lines. +``` + +这看起来很简单。`vector`被填满、返回和调用。然而,作为关心性能的高效程序员,这方面的一些问题困扰着我们:在return语句中,由于使用了值语义,`vector`在销毁之前不久就被复制到一个新`vector`中。 + +> 在现代C ++中,这不再是严格的要求了。 C ++ 11引入了移动语义的概念,其中将原点保留在有效状态(以便仍然可以正确销毁)但未指定状态。 对于编译器而言,返回调用是最容易优化以优化语义移动的情况,因为它知道在进行任何进一步访问之前不久将销毁源。 但是,该示例的目的是说明为什么人们在80年代末和90年代初发明了一大堆垃圾收集的语言,而在那个时候C ++ move语义不可用。 + +对于数据量比较大的文件,这可能会变得昂贵。 让我们对其进行优化,只返回一个指针。 语法进行了一些更改,但其他代码相同: + +```cpp +vector * read_lines_from_file(string &file_name) { + vector * lines; + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines->push_back(line); + } + + file_handle.close(); + + return lines; +} +int main(int argc, char* argv[]) { + // get file name from the first argument + string file_name (argv[1]); + int count = read_lines_from_file(file_name).size(); + cout << "File " << file_name << " contains " << count << " lines."; + + return 0; +} +``` + +输出: + +``` +Segmentation fault (core dumped) +``` + +程序崩溃!我们只需要将上述的`lines`进行内存分配: + +```cpp +vector * lines = new vector; +``` + +这样就可以运行了! + +不幸的是,尽管这看起来很完美,但它仍然有一个缺陷:它会泄露内存。在C++中,指向堆的指针在不再需要后必须手动删除;否则,一旦最后一个指针超出范围,该内存将变得不可用,并且直到进程结束时操作系统对其进行管理后才会恢复。惯用的现代C++将在这里使用`unique_ptr`,它实现了期望的行为。它删除指针超出范围时指向的对象。然而,这种行为直到C++11才成为语言的一部分。 + +在这里,可以直接使用C++11之前的语法,只是把main中改一下即可: + +```cpp +vector * read_lines_from_file(string &file_name) { + vector * lines = new vector; + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines->push_back(line); + } + + file_handle.close(); + + return lines; +} + +int main(int argc, char* argv[]) { + // get file name from the first argument + string file_name (argv[1]); + vector * file_lines = read_lines_from_file(file_name); + int count = file_lines->size(); + delete file_lines; + cout << "File " << file_name << " contains " << count << " lines."; + + return 0; +} +``` + +手动去分配内存与释放内存。 + +**不幸的是,随着程序扩展到上述范围之外,很快就变得更加难以推理指针应该在何时何地被删除。当一个函数返回指针时,你现在拥有它吗?您应该在完成后自己删除它,还是它属于某个稍后将被一次性释放的数据结构?一方面出错,内存泄漏,另一方面出错,你已经破坏了正在讨论的数据结构和其他可能的数据结构,因为它们试图取消引用现在不再有效的指针。** + +## 2.“使用垃圾收集器,flyboy!” + +垃圾收集器不是一项新技术。 它们由John McCarthy在1959年为Lisp发明。 1980年,随着Smalltalk-80的出现,垃圾收集开始成为主流。 但是,1990年代代表了该技术的真正发芽:在1990年至2000年之间,发布了多种语言,所有语言都使用一种或另一种垃圾回收:Haskell,Python,Lua,Java,JavaScript,Ruby,OCaml 和C#是最著名的。 + +什么是垃圾收集? 简而言之,这是一组用于自动执行手动内存管理的技术。 它通常作为具有手动内存管理的语言(例如C和C ++)的库提供,但在需要它的语言中更常用。 最大的优点是程序员根本不需要考虑内存。 都被抽象了。 例如,相当于我们上面的文件读取代码的Python就是这样: + +```python +def read_lines_from_file(file_name): + lines = [] + with open(file_name) as fp: + for line in fp: + lines.append(line) + return lines + +if __name__ == '__main__': + import sys + file_name = sys.argv[1] + count = len(read_lines_from_file(file_name)) + print("File {} contains {} lines.".format(file_name, count)) +``` + +行数组是在第一次分配给它时出现的,并且不复制到调用范围就返回。 由于时间不确定,它会在超出该范围后的某个时间被垃圾收集器清理。 有趣的是,在Python中,用于非内存资源的RAII不是惯用语言。 允许-我们可以简单地编写`fp = open(file_name)`而不是使用with块,然后让GC清理。 但是建议的模式是在可能的情况下使用上下文管理器,以便可以在确定的时间释放它们。 + +尽管简化了内存管理,但要付出很大的代价。 在引用计数垃圾回收中,所有变量赋值和作用域出口都会获得少量成本来更新引用。 在标记清除系统中,在GC清除内存的同时,所有程序的执行都以不可预测的时间间隔暂停。 这通常称为世界停止事件。 同时使用这两种系统的Python之类的实现都会受到两种惩罚。 这些问题降低了垃圾收集语言在性能至关重要或需要实时应用程序的情况下的适用性。 即使在以下玩具程序上,也可以看到实际的性能下降: + +```cpp +$ make cpp && time ./c++ makefile +g++ -o c++ c++.cpp +File makefile contains 38 lines. +real 0m0.016s +user 0m0.000s +sys 0m0.015s + +$ time python3 python3.py makefile +File makefile contains 38 lines. + +real 0m0.041s +user 0m0.015s +sys 0m0.015s +``` + +Python版本的实时时间几乎是C ++版本的三倍。 尽管并非所有这些差异都可以归因于垃圾收集,但它仍然是可观的。 + +## 3.所有权:RAII觉醒 + +我们知道对象的生存期由其范围决定。 但是,有时我们需要创建一个对象,该对象与创建对象的作用域无关,这是有用的,或者很有用。 在C ++中,运算符new用于创建这样的对象。 为了销毁对象,可以使用运算符delete。 由new操作员创建的对象是动态分配的,即在动态内存(也称为堆或空闲存储)中分配。 因此,由new创建的对象将继续存在,直到使用delete将其明确销毁为止。 + +使用new和delete时可能发生的一些错误是: + +- 对象(或内存)泄漏:使用new分配对象,而忘记删除该对象。 + +- 过早删除(或悬挂引用):持有指向对象的另一个指针,删除该对象,然而还有其他指针在引用它。 + +- 双重删除:尝试两次删除一个对象。 + +通常,范围变量是首选。 但是,RAII可以用作new和delete的替代方法,以使对象独立于其范围而存在。 这种技术包括将指针分配到在堆上分配的对象,并将其放在句柄/管理器对象中。 后者具有一个析构函数,将负责销毁该对象。 这将确保该对象可用于任何想要访问它的函数,并且该对象在句柄对象的生存期结束时将被销毁,而无需进行显式清理。 + +来自C ++标准库的使用RAII的示例为std :: string和std :: vector。 + +考虑这段代码: + +```cpp +void fn(const std::string& str) +{ + std::vector vec; + for (auto c : str) + vec.push_back(c); + // do something +} +``` + +当创建`vector`,并将元素推入`vector`时,您不必担心分配和取消分配此类元素内存。 `vector`使用new为其堆上的元素分配空间,并使用delete释放该空间。 作为vector的用户,您无需关心实现细节,并且会相信vector不会泄漏。 在这种情况下,向量是其元素的句柄对象。 + +标准库中使用RAII的其他示例是std :: shared_ptr,std :: unique_ptr和std :: lock_guard。 + +该技术的另一个名称是SBRM,是范围绑定资源管理的缩写。 + +现在,我们将上述读取文件例子,进行修改: + +```cpp +#include +#include +#include +#include +#include + +using namespace std; +unique_ptr> read_lines_from_file(string &file_name) { + unique_ptr> lines(new vector); + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines->push_back(line); + } + + file_handle.close(); + + return lines; +} +int main(int argc, char* argv[]) { + // get file name from the first argument + string file_name (argv[1]); + int count = read_lines_from_file(file_name).get()->size(); + cout << "File " << file_name << " contains " << count << " lines."; + + return 0; +} +``` + +## 4.只有在最后,你才意识到RAII的真正力量。 + +自从编译器发明以来,手动内存管理是程序员一直在想办法避免的噩梦。 RAII是一种很有前途的模式,但由于没有一些奇怪的解决方法,它根本无法用于堆分配的对象,因此在C ++中会受到影响。 因此,在90年代出现了垃圾收集语言的爆炸式增长,旨在使程序员生活更加愉快,即使以性能为代价。 + +最后,RAII总结如下: + +- 资源在析构函数中被释放 + +- 该类的实例是堆栈分配的 +- 资源是在构造函数中获取的。 + +RAII代表“资源获取是初始化”。 + +常见的例子有: + +- 文件操作 + +- 智能指针 + +- 互斥量 + +## 5.参考文章 + +> 1.https://www.toptal.com/software/eliminating-garbage-collector#remote-developer-job + +> 2.https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii \ No newline at end of file diff --git a/codingStyleIdioms/3_RAII/RAII_fstram.cpp b/codingStyleIdioms/3_RAII/RAII_fstram.cpp new file mode 100644 index 00000000..07f5308e --- /dev/null +++ b/codingStyleIdioms/3_RAII/RAII_fstram.cpp @@ -0,0 +1,29 @@ +// +// Created by light on 19-12-9. +// +#include +#include + +// RAII 资源获取即初始化,例2 + +// C ++保证在对象超出范围时调用析构函数,而不管控制如何离开该范围。 +// 即使抛出异常,所有本地对象也会超出范围,因此它们的相关资源将被清除。 + +void foo() { + std::fstream file("bar.txt"); // open a file "bar.txt" + if (rand() % 2) { + // if this exception is thrown, we leave the function, and so + // file's destructor is called, which closes the file handle. + throw std::exception(); + } + // if the exception is not called, we leave the function normally, and so + // again, file's destructor is called, which closes the file handle. +} + +int main() { + try { + foo(); + } catch (std::exception) { + puts("exception!"); + } +} \ No newline at end of file diff --git a/codingStyleIdioms/3_RAII/c++_example.cpp b/codingStyleIdioms/3_RAII/c++_example.cpp new file mode 100644 index 00000000..57ae698e --- /dev/null +++ b/codingStyleIdioms/3_RAII/c++_example.cpp @@ -0,0 +1,13 @@ +// +// Created by light on 19-12-12. +// +#include +int main() { + std::string str = std::string ("toptal"); + std::cout << "string object: " << str << " @ " << &str << "\n"; + + str += ".com"; + std::cout << "string object: " << str << " @ " << &str << "\n"; + + return(0); +} \ No newline at end of file diff --git a/codingStyleIdioms/3_RAII/c++_example1.cpp b/codingStyleIdioms/3_RAII/c++_example1.cpp new file mode 100644 index 00000000..da28c108 --- /dev/null +++ b/codingStyleIdioms/3_RAII/c++_example1.cpp @@ -0,0 +1,66 @@ +// +// Created by light on 19-12-12. +// +#include +#include +#include +#include + +using namespace std; +vector read_lines_from_file(string &file_name) { + vector lines; + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines.push_back(line); + } + + file_handle.close(); + + return lines; +} +vector * read_lines_from_file1(string &file_name) { + vector * lines; + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines->push_back(line); + } + + file_handle.close(); + + return lines; +} +vector * read_lines_from_file1_1(string &file_name) { + vector * lines=new vector; + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines->push_back(line); + } + + file_handle.close(); + + return lines; +} +int main() { + // get file name from the first argument + string file_name ("/home/light/CLionProjects/Morden_C++/CMakeLists.txt"); + int count = read_lines_from_file(file_name).size(); + cout << "File " << file_name << " contains " << count << " lines."; + + cout<size(); +// cout << "File " << file_name << " contains " << count1 << " lines."; + string file_name1 ("/home/light/CLionProjects/Morden_C++/CMakeLists.txt"); + int count1 = read_lines_from_file1_1(file_name1)->size(); + cout << "File " << file_name << " contains " << count1 << " lines."; + return 0; +} \ No newline at end of file diff --git a/codingStyleIdioms/3_RAII/c++_example2.cpp b/codingStyleIdioms/3_RAII/c++_example2.cpp new file mode 100644 index 00000000..f92c82e8 --- /dev/null +++ b/codingStyleIdioms/3_RAII/c++_example2.cpp @@ -0,0 +1,32 @@ +// +// Created by light on 19-12-12. +// +#include +#include +#include +#include +#include + +using namespace std; +unique_ptr> read_lines_from_file(string &file_name) { + unique_ptr> lines(new vector); + string line; + + ifstream file_handle (file_name.c_str()); + while (file_handle.good() && !file_handle.eof()) { + getline(file_handle, line); + lines->push_back(line); + } + + file_handle.close(); + + return lines; +} +int main() { + // get file name from the first argument + string file_name ("/home/light/CLionProjects/Morden_C++/CMakeLists.txt"); + int count = read_lines_from_file(file_name).get()->size(); + cout << "File " << file_name << " contains " << count << " lines."; + cout< +#include +#include +int main() { + char *str = (char *) malloc(7); + strcpy(str, "toptal"); + printf("char array = \"%s\" @ %u\n", str, str); + + str = (char *) realloc(str, 11); + strcat(str, ".com"); + printf("char array = \"%s\" @ %u\n", str, str); + + free(str); + + return(0); +} \ No newline at end of file diff --git a/codingStyleIdioms/4_copy-swap/README.md b/codingStyleIdioms/4_copy-swap/README.md new file mode 100644 index 00000000..bef2bdfa --- /dev/null +++ b/codingStyleIdioms/4_copy-swap/README.md @@ -0,0 +1,204 @@ +> 为什么我们需要复制和交换习惯? + +任何管理资源的类(包装程序,如智能指针)都需要实现big three。尽管拷贝构造函数和析构函数的目标和实现很简单。 + +但是复制分配运算符无疑是最细微和最困难的。 + +> 应该怎么做?需要避免什么陷阱? + +copy-swap是解决方案,可以很好地协助赋值运算符实现两件事:避免代码重复,并提供强大的**异常保证**。 + +>它是如何工作的? + +**从概念上讲,它通过使用拷贝构造函数的功能来创建数据的本地副本,然后使用交换功能获取复制的数据,将旧数据与新数据交换来工作。然后,临时副本将销毁,并随身携带旧数据。我们剩下的是新数据的副本。** + +为了使用copy-swap,我们需要三件事: + +- 一个有效的拷贝构造函数 +- 一个有效的析构函数(两者都是任何包装程序的基础,因此无论如何都应完整)以及交换功能。 + +交换函数是一种不抛异常函数,它交换一个类的两个对象或者成员。我们可能很想使用std :: swap而不是提供我们自己的方法,但这是不可能的。 std :: swap在实现中使用了copy-constructor和copy-assignment运算符,我们最终将尝试根据自身定义赋值运算符! + +(不仅如此,对swap的无条件调用将使用我们的自定义swap运算符,从而跳过了std :: swap会导致的不必要的类构造和破坏。) + +具体例子如下: + +```cpp +namespace A { + template + class smart_ptr { + public: + smart_ptr() noexcept : ptr_(new T()) { + + } + + smart_ptr(const T &ptr) noexcept : ptr_(new T(ptr)) { + + } + + smart_ptr(smart_ptr &rhs) noexcept { + ptr_ = rhs.release(); // 释放所有权,此时rhs的ptr_指针为nullptr + } + + void swap(smart_ptr &rhs) noexcept { // noexcept == throw() 保证不抛出异常 + using std::swap; + swap(ptr_, rhs.ptr_); + } + + T *release() noexcept { + T *ptr = ptr_; + ptr_ = nullptr; + return ptr; + } + + T *get() const noexcept { + return ptr_; + } + + private: + T *ptr_; + }; + +// 提供一个非成员swap函数for ADL(Argument Dependent Lookup) + template + void swap(A::smart_ptr &lhs, A::smart_ptr &rhs) noexcept { + lhs.swap(rhs); + } +} +// 注释开启,会引发ADL冲突 +//namespace std { +// // 提供一个非成员swap函数for ADL(Argument Dependent Lookup) +// template +// void swap(A::smart_ptr &lhs, A::smart_ptr &rhs) noexcept { +// lhs.swap(rhs); +// } +// +//} + +int main() { + + using std::swap; + A::smart_ptr s1("hello"), s2("world"); + // 交换前 + std::cout << *s1.get() << " " << *s2.get() << std::endl; + swap(s1, s2); // 这里swap 能够通过Koenig搜索或者说ADL根据s1与s2的命名空间来查找swap函数 + // 交换后 + std::cout << *s1.get() << " " << *s2.get() << std::endl; + s1=s2; +} +``` + +现在为了让上述的`s1=s2`完成工作,必须实现赋值运算符。 + +> 方法1 + +为了避免自赋值,通常采用下面写法 。 + +不好! + +不具备异常安全,只具备自我赋值安全性 + +```cpp +smart_ptr &operator=(const smart_ptr &rhs) { + if (*this != rhs) { + delete ptr_; + ptr_ = new T(rhs.ptr_); // 当new 发生异常,此时ptr_指向的而是一块被删除区域,而不是被赋值对象的区域 + return *this; + } + return *this; +} +``` + +> 方法2 + +如果new出现异常,ptr_会保持原装! 也可以处理自我赋值! 还是不够好! + +**这样就会导致代码膨胀,于是导致了另一个问题:代码冗余** + +```cpp +// 方法2:如果new出现异常,ptr_会保持原装! 也可以处理自我赋值! 还是不够好! +smart_ptr &operator=(const smart_ptr &rhs) { + T *origin = ptr_; + ptr_ = new T(rhs.ptr_); + delete origin; + return *this; +} +``` + +> 方法3 + +copy and swap 很好! + +```cpp +smart_ptr &operator=(smart_ptr &rhs) noexcept { + smart_ptr tmp(rhs); + swap(tmp); + return *this; +} +``` + +> 方法4 + +改为传值,同方法3! + +```cpp +smart_ptr &operator=(smart_ptr rhs) noexcept { + swap(rhs); + return *this; +} +``` + +> C++11 move + +我们在big three上加上move ctor与move assignment就构成了big five。 + +此时再次拓展上述的代码: + +```cpp +// move ctor +smart_ptr(smart_ptr &&rhs) noexcept { + std::cout << "move ctor" << std::endl; + ptr_ = rhs.ptr_; + if (ptr_) + rhs.ptr_ = nullptr; +} + +// move assignment +smart_ptr &operator=(smart_ptr &&rhs) noexcept { + std::cout << "move assignment" << std::endl; + smart_ptr tmp(rhs); + swap(rhs); + return *this; +} +``` + +**实际上,我们比那个不需要多写代码move assignment,copy-and-swap 技巧 和 move-and-swap 技巧是共享同一个函数的。**当copy构造为上述的方法4时,**对于C++ 11,编译器会依据参数是左值还是右值在拷贝构造函数和移动构造函数间进行选择:** + +```cpp +smart_ptr &operator=(smart_ptr rhs) noexcept { + swap(rhs); + return *this; +} +``` + +所以当这个同上述写的 + +```cpp +smart_ptr &operator=(smart_ptr &&rhs) noexcept{} +``` + +同时存在,就会出现error: ambiguous overload for ‘operator=’ 。 + +调用处如下: + +```cpp +A::smart_ptr s1("hello"), s2("world"); +A::smart_ptr s3 = s1; +A::smart_ptr s4 = std::move(s1); +``` + +- 如果是 s3 = s1,这样就会调用拷贝构造函数来初始化other(因为s1是左值),赋值操作符会与新创建的对象交换数据,深度拷贝。这就是copy and swap 惯用法的定义:构造一个副本,与副本交换数据,并让副本在作用域内自动销毁。 +- 如果是s4 = std::move(s1),这样就会调用移动构造函数来初始化rhs(因为std::move(s1)是右值),所以这里没有深度拷贝,只有高效的数据转移。 + +因此也可以称呼它为“统一赋值操作符”,因为它合并了"拷贝赋值"与"移动赋值"。 + diff --git a/codingStyleIdioms/4_copy-swap/copy-swapAndADL.cpp b/codingStyleIdioms/4_copy-swap/copy-swapAndADL.cpp new file mode 100644 index 00000000..e289b1d5 --- /dev/null +++ b/codingStyleIdioms/4_copy-swap/copy-swapAndADL.cpp @@ -0,0 +1,119 @@ +// +// Created by light on 19-12-9. +// +#include + +// copy and swap : https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom +// ADL : https://stackoverflow.com/questions/8111677/what-is-argument-dependent-lookup-aka-adl-or-koenig-lookup + +namespace A { + template + class smart_ptr { + public: + smart_ptr() noexcept : ptr_(new T()) { + + } + + smart_ptr(const T &ptr) noexcept : ptr_(new T(ptr)) { + + } + + smart_ptr(smart_ptr &rhs) noexcept { + std::cout << "copy ctor" << std::endl; + ptr_ = rhs.release(); // 释放所有权,此时rhs的ptr_指针为nullptr + } + + // 方法1:为了避免自赋值,通常采用下面写法 不好! 不具备异常安全,只具备自我赋值安全性 +// smart_ptr &operator=(const smart_ptr &rhs) { +// if (*this != rhs) { +// delete ptr_; +// ptr_ = new T(rhs.ptr_); // 当new 发生异常,此时ptr_指向的而是一块被删除区域,而不是被赋值对象的区域 +// return *this; +// } +// return *this; +// } + // 方法2:如果new出现异常,ptr_会保持原装! 也可以处理自我赋值! 还是不够好! +// smart_ptr &operator=(const smart_ptr &rhs) { +// T *origin = ptr_; +// ptr_ = new T(rhs.ptr_); +// delete origin; +// return *this; +// } + // 方法3:copy and swap 很好! +// smart_ptr &operator=(smart_ptr &rhs) noexcept { +// smart_ptr tmp(rhs); +// swap(tmp); +// return *this; +// } + + // 方法4:同方法3,改为传值 + // 既适用于copy ctor也适用于 move ctor + smart_ptr &operator=(smart_ptr rhs) noexcept { + swap(rhs); + return *this; + } + // move ctor + smart_ptr(smart_ptr &&rhs) noexcept { + std::cout << "move ctor" << std::endl; + ptr_ = rhs.ptr_; + if (ptr_) + rhs.ptr_ = nullptr; + } + + // move assignment +// smart_ptr &operator=(smart_ptr &&rhs) noexcept { +// std::cout << "move assignment" << std::endl; +// smart_ptr tmp(rhs); +// swap(rhs); +// return *this; +// } + + void swap(smart_ptr &rhs) noexcept { // noexcept == throw() 保证不抛出异常 + using std::swap; + swap(ptr_, rhs.ptr_); + } + + T *release() noexcept { + T *ptr = ptr_; + ptr_ = nullptr; + return ptr; + } + + T *get() const noexcept { + return ptr_; + } + + private: + T *ptr_; + }; + +// 提供一个非成员swap函数for ADL(Argument Dependent Lookup) + template + void swap(A::smart_ptr &lhs, A::smart_ptr &rhs) noexcept { + lhs.swap(rhs); + } +} +// 注释开启,会引发ADL冲突 +//namespace std { +// // 提供一个非成员swap函数for ADL(Argument Dependent Lookup) +// template +// void swap(A::smart_ptr &lhs, A::smart_ptr &rhs) noexcept { +// lhs.swap(rhs); +// } +// +//} + +int main() { + + using std::swap; + A::smart_ptr s1("hello"), s2("world"); + // 交换前 + std::cout << *s1.get() << " " << *s2.get() << std::endl; + swap(s1, s2); // 这里swap 能够通过Koenig搜索或者说ADL根据s1与s2的命名空间来查找swap函数 + // 交换后 + std::cout << *s1.get() << " " << *s2.get() << std::endl; +// s1 = s2; + + A::smart_ptr s3 = s1; + A::smart_ptr s4 = std::move(s1); +} \ No newline at end of file diff --git a/codingStyleIdioms/5_pImpl/.a.cpp.un~ b/codingStyleIdioms/5_pImpl/.a.cpp.un~ new file mode 100644 index 00000000..16b83e6d Binary files /dev/null and b/codingStyleIdioms/5_pImpl/.a.cpp.un~ differ diff --git a/codingStyleIdioms/5_pImpl/.pimplTime.cpp.un~ b/codingStyleIdioms/5_pImpl/.pimplTime.cpp.un~ new file mode 100644 index 00000000..76271a23 Binary files /dev/null and b/codingStyleIdioms/5_pImpl/.pimplTime.cpp.un~ differ diff --git a/codingStyleIdioms/5_pImpl/.timePimpl.cpp.un~ b/codingStyleIdioms/5_pImpl/.timePimpl.cpp.un~ new file mode 100644 index 00000000..488ccca0 Binary files /dev/null and b/codingStyleIdioms/5_pImpl/.timePimpl.cpp.un~ differ diff --git a/codingStyleIdioms/5_pImpl/README.md b/codingStyleIdioms/5_pImpl/README.md new file mode 100644 index 00000000..368a0925 --- /dev/null +++ b/codingStyleIdioms/5_pImpl/README.md @@ -0,0 +1,57 @@ +# C++惯用法之pImpl + +“指向实现的指针”或“pImpl”是一种 C++ 编程技巧,它将类的实现细节从对象表示中移除,放到一个分离的类中,并以一个不透明的指针进行访问。 + +使用pImpl惯用法的原因如下: + +考虑如下例子: + +```cpp +class X +{ +private: + C c; + D d; +} ; +``` + +变成pImpl就是下面这样子 + +```cpp +class X +{ +private: + struct XImpl; + XImpl* pImpl; +}; +``` + +CPP定义: + +```cpp +struct X::XImpl +{ + C c; + D d; +}; +``` + +- 二进制兼容性 + +开发库时,可以在不破坏与客户端的二进制兼容性的情况下向XImpl添加/修改字段(这将导致崩溃!)。 由于在向Ximpl类添加新字段时X类的二进制布局不会更改,因此可以安全地在次要版本更新中向库添加新功能。 + +当然,您也可以在不破坏二进制兼容性的情况下向X / XImpl添加新的公共/私有非虚拟方法,但这与标准的标头/实现技术相当。 + +- 数据隐藏 + +如果您正在开发一个库,尤其是专有库,则可能不希望公开用于实现库公共接口的其他库/实现技术。 要么是由于知识产权问题,要么是因为您认为用户可能会被诱使对实现进行危险的假设,或者只是通过使用可怕的转换技巧来破坏封装。 PIMPL解决/缓解了这一难题。 + +- 编译时间 + +编译时间减少了,因为当您向XImpl类添加/删除字段和/或方法时(仅映射到标准技术中添加私有字段/方法的情况),仅需要重建X的源(实现)文件。 实际上,这是一种常见的操作。 + +使用标准的标头/实现技术(没有PIMPL),当您向X添加新字段时,曾经重新分配X(在堆栈或堆上)的每个客户端都需要重新编译,因为它必须调整分配的大小 。 好吧,每个从未分配X的客户端也都需要重新编译,但这只是开销(客户端上的结果代码是相同的)。 + + + +> https://stackoverflow.com/questions/8972588/is-the-pimpl-idiom-really-used-in-practices \ No newline at end of file diff --git a/codingStyleIdioms/5_pImpl/a.cpp~ b/codingStyleIdioms/5_pImpl/a.cpp~ new file mode 100644 index 00000000..7a865b07 --- /dev/null +++ b/codingStyleIdioms/5_pImpl/a.cpp~ @@ -0,0 +1,24 @@ +#include +using namespace std; + +class C { + +public: + virtual void print(); +}; + + +class CC:public C { +public: + void print() { + cout<<"CC"<(new C); + //c->print(); +} + + diff --git a/codingStyleIdioms/5_pImpl/noPimpl.cpp b/codingStyleIdioms/5_pImpl/noPimpl.cpp new file mode 100644 index 00000000..8c093680 --- /dev/null +++ b/codingStyleIdioms/5_pImpl/noPimpl.cpp @@ -0,0 +1,23 @@ +#include +#include +using namespace std; + +class C { + vector v; + string s; +}; +class D { + string s; +}; + +class X { +private: + C c; + D d; +}; + + +int main() { + + X x; +} diff --git a/codingStyleIdioms/5_pImpl/pimpl.cpp b/codingStyleIdioms/5_pImpl/pimpl.cpp new file mode 100644 index 00000000..4e699006 --- /dev/null +++ b/codingStyleIdioms/5_pImpl/pimpl.cpp @@ -0,0 +1,48 @@ +// +// Created by light on 19-12-9. +// + +#include +using namespace std; + +// pImpl: Pointer-to-Implementation + +class private_foo; +class foo { +public: + foo(); + + ~foo(); + + void bar(); + +private: + private_foo *pImpl; +}; + +class private_foo { +public: + void bar() { + cout<<"private_foo invoke bar funciton."<bar(); +} + + +int main() { + foo f; + f.bar(); +} \ No newline at end of file diff --git a/codingStyleIdioms/5_pImpl/pimplTime.cpp b/codingStyleIdioms/5_pImpl/pimplTime.cpp new file mode 100644 index 00000000..b6db59ea --- /dev/null +++ b/codingStyleIdioms/5_pImpl/pimplTime.cpp @@ -0,0 +1,26 @@ +#include +#include +using namespace std; + +class C { + vector v; + string s; +}; +class D { + string s; +}; + +class X { +private: + struct XImpl; + XImpl* pImpl; +}; + +struct X::XImpl { + C c; + D d; +}; + +int main() { + X x; +} diff --git a/codingStyleIdioms/5_pImpl/pimplTime.cpp~ b/codingStyleIdioms/5_pImpl/pimplTime.cpp~ new file mode 100644 index 00000000..7fc3dd2d --- /dev/null +++ b/codingStyleIdioms/5_pImpl/pimplTime.cpp~ @@ -0,0 +1,11 @@ +#include "pimplTime.h" + +struct X::XImpl { + C c; + D d; +} + +int main() { + X x; + +} diff --git a/codingStyleIdioms/5_pImpl/pimplTime.h b/codingStyleIdioms/5_pImpl/pimplTime.h new file mode 100644 index 00000000..59a3e119 --- /dev/null +++ b/codingStyleIdioms/5_pImpl/pimplTime.h @@ -0,0 +1,19 @@ +#include +#include +using namespace std; + +class C { + vector v; + string s; +}; +class D { + string s; +}; + +class X { +private: + struct XImpl; + XImpl* pImpl; +}; + + diff --git a/codingStyleIdioms/5_pImpl/timePimpl.cpp~ b/codingStyleIdioms/5_pImpl/timePimpl.cpp~ new file mode 100644 index 00000000..41450d51 --- /dev/null +++ b/codingStyleIdioms/5_pImpl/timePimpl.cpp~ @@ -0,0 +1 @@ +#include <> diff --git a/codingStyleIdioms/README.md b/codingStyleIdioms/README.md new file mode 100644 index 00000000..9cecdecb --- /dev/null +++ b/codingStyleIdioms/README.md @@ -0,0 +1,14 @@ +# 你最喜欢的c++编程风格惯用法是什么? + +在stackoverflow上找到了一篇文章,写的蛮好的,地址如下: + +> https://stackoverflow.com/questions/276173/what-are-your-favorite-c-coding-style-idioms#comment60171463_2034439 + +由于是英文的,且比较重要,于是总结成下面几条! + +- [1.类初始化列表](./1_classInitializers) +- [2.枚举类替换命名空间](./2_enumclass_namespace) +- [3.RAII(资源获取即初始化)](./3_RAII) +- [4.copy and swap](./4_copy-swap) +- [5.pImpl(指针指向具体实现)](./5_pImpl) + diff --git a/morden_C++_30/.CMakeLists.txt.un~ b/modern_C++_30/.CMakeLists.txt.un~ similarity index 100% rename from morden_C++_30/.CMakeLists.txt.un~ rename to modern_C++_30/.CMakeLists.txt.un~ diff --git a/morden_C++_30/CMakeLists.txt b/modern_C++_30/CMakeLists.txt similarity index 100% rename from morden_C++_30/CMakeLists.txt rename to modern_C++_30/CMakeLists.txt diff --git a/morden_C++_30/CMakeLists.txt~ b/modern_C++_30/CMakeLists.txt~ similarity index 100% rename from morden_C++_30/CMakeLists.txt~ rename to modern_C++_30/CMakeLists.txt~ diff --git a/morden_C++_30/RAII/RAII.cpp b/modern_C++_30/RAII/RAII.cpp similarity index 100% rename from morden_C++_30/RAII/RAII.cpp rename to modern_C++_30/RAII/RAII.cpp diff --git a/morden_C++_30/RAII/heap.cpp b/modern_C++_30/RAII/heap.cpp similarity index 100% rename from morden_C++_30/RAII/heap.cpp rename to modern_C++_30/RAII/heap.cpp diff --git a/morden_C++_30/RAII/stack.cpp b/modern_C++_30/RAII/stack.cpp similarity index 100% rename from morden_C++_30/RAII/stack.cpp rename to modern_C++_30/RAII/stack.cpp