diff --git a/book/en-us/05-pointers.md b/book/en-us/05-pointers.md index d3e8731..357098a 100644 --- a/book/en-us/05-pointers.md +++ b/book/en-us/05-pointers.md @@ -6,10 +6,199 @@ order: 5 # Chapter 05 Smart Pointers and Memory Management +[TOC] + +## 5.1 RAII and Reference Counting + +Programmers who understand `Objective-C`/`Swift`/`JavaScript` should know the concept of reference counting. The reference count is counted to prevent memory leaks. +The basic idea is to count the number of dynamically allocated objects. Whenever you add a reference to the same object, the reference count of the referenced object is incremented once. +Each time a reference is deleted, the reference count is decremented by one. When the reference count of an object is reduced to zero, the pointed heap memory is automatically deleted. + +In traditional C++, "remembering" to manually release resources is not always a best practice. Because we are likely to forget to release resources and lead to leakage. +So the usual practice is that for an object, we apply for space when constructor, and free space when the destructor (called when leaving the scope). +That is, we often say that the RAII resource acquisition is the initialization technology. + +There are exceptions to everything, we always have the need to allocate objects on free storage. In traditional C++ we have to use `new` and `delete` to "remember" to release resources. C++11 introduces the concept of smart pointers, using the idea of ​​reference counting, so that programmers no longer need to care about manually releasing memory. +These smart pointers include `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`, which need to include the header file ``. + +> Note: The reference count is not garbage collection. The reference count can recover the objects that are no longer used as soon as possible, and will not cause long waits during the recycling process. +> More clearly and clearly indicate the life cycle of resources. + +## 5.2 `std::shared_ptr` + +`std::shared_ptr` is a smart pointer that records how many `shared_ptr` points to an object, eliminating the display call `delete`, which automatically deletes the object when the reference count becomes zero. + +But not enough, because using `std::shared_ptr` still needs to be called with `new`, which makes the code a certain degree of asymmetry. + +`std::make_shared` can be used to eliminate the explicit use of `new`, so `std::make_shared` will allocate the objects in the generated parameters. +And return the `std::shared_ptr` pointer of this object type. For example: + +```cpp +#include +#include +void foo(std::shared_ptr i) +{ + (*i)++; +} +int main() +{ + // auto pointer = new int(10); // illegal, no direct assignment + // Constructed a std::shared_ptr + auto pointer = std::make_shared(10); + foo(pointer); + std::cout << *pointer << std::endl; // 11 + // The shared_ptr will be destructed before leaving the scope + return 0; +} +``` + +`std::shared_ptr` can get the raw pointer through the `get()` method and reduce the reference count by `reset()`. +And see the reference count of an object by `use_count()`. E.g: + +```cpp +auto pointer = std::make_shared(10); +auto pointer2 = pointer; // reference count+1 +auto pointer3 = pointer; // reference count+1 +int *p = pointer.get(); // no increase of reference count +std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 +std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3 +std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3 + +pointer2.reset(); +std::cout << "reset pointer2:" << std::endl; +std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 +std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 has reset +std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2 +pointer3.reset(); +std::cout << "reset pointer3:" << std::endl; +std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 +std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0 +std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 has reset +``` + +## 5.3 `std::unique_ptr` + +`std::unique_ptr` is an exclusive smart pointer that prohibits other smart pointers from sharing the same object, thus keeping the code safe: + +```cpp +std::unique_ptr pointer = std::make_unique(10); // make_unique was introduced in C++14 +std::unique_ptr pointer2 = pointer; // illegal +``` + +> `make_unique` is not complicated. C++11 does not provide `std::make_unique`, which can be implemented by itself: +> +> ```cpp +> template +> std::unique_ptr make_unique( Args&& ...args ) { +> return std::unique_ptr( new T( std::forward(args)... ) ); +> } +> ``` +> +> As for why it wasn't provided, Herb Sutter, chairman of the C++ Standards Committee, mentioned in his [blog](https://herbsutter.com/gotw/_102/) that it was because they were forgotten. + +Since it is monopolized, in other words, it cannot be copied. However, we can use `std::move` to transfer it to other `unique_ptr`, for example: + +```cpp +#include +#include + +struct Foo { + Foo() { std::cout << "Foo::Foo" << std::endl; } + ~Foo() { std::cout << "Foo::~Foo" << std::endl; } + void foo() { std::cout << "Foo::foo" << std::endl; } +}; + +void f(const Foo &) { + std::cout << "f(const Foo&)" << std::endl; +} + +int main() { + std::unique_ptr p1(std::make_unique()); + + // p1 is not empty, prints + if (p1) p1->foo(); + { + std::unique_ptr p2(std::move(p1)); + + // p2 is not empty, prints + f(*p2); + + // p2 is not empty, prints + if(p2) p2->foo(); + + // p1 is empty, no prints + if(p1) p1->foo(); + + p1 = std::move(p2); + + // p2 is empty, no prints + if(p2) p2->foo(); + std::cout << "p2 was destroied" << std::endl; + } + // p1 is not empty, prints + if (p1) p1->foo(); + + // Foo instance will be destroied when leaving the scope +} +``` + +## 5.4 `std::weak_ptr` + +If you think about `std::shared_ptr` carefully, you will still find that there is still a problem that resources cannot be released. Look at the following example: + +```cpp +#include +#include + +class A; +class B; + +class A { +public: + std::shared_ptr pointer; + ~A() { + std::cout << "A was destroied" << std::endl; + } +}; +class B { +public: + std::shared_ptr pointer; + ~B() { + std::cout << "B was destroied" << std::endl; + } +}; +int main() { + std::shared_ptr a = std::make_shared(); + std::shared_ptr b = std::make_shared(); + a->pointer = b; + b->pointer = a; + + return 0; +} +``` + +The result is that A and B will not be destroyed. This is because the pointer inside a, b also references `a, b`, which makes the reference count of `a, b` become 2, leaving the scope. When the `a, b` smart pointer is destructed, it can only cause the reference count of this area to be decremented by one. This causes the memory area reference count pointed to by the `a, b` object to be non-zero, but the external has no The way to find this area, it also caused a memory leak, as shown in Figure 5.1: + +![Figure 5.1](../../assets/figures/pointers1.png) + +The solution to this problem is to use the weak reference pointer `std::weak_ptr`, which is a weak reference (compared to `std::shared_ptr` is a strong reference). A weak reference does not cause an increase in the reference count. When a weak reference is used, the final release process is shown in Figure 5.2: + +![Figure 5.2](../../assets/figures/pointers2.png) + +In the above figure, only B is left in the last step, and B does not have any smart pointers to reference it, so this memory resource will also be released. + +`std::weak_ptr` has no `*` operator and `->` operator, so it can't operate on resources. Its only function is to check if `std::shared_ptr` exists, its `expired() The ` method can return `true` when the resource is not released, otherwise it returns `false`. + +## Conclusion + +The technology of smart pointers is not novel. It is a common technology in many languages. Modern C++ introduces this technology, which eliminates the abuse of `new`/`delete` to a certain extent. It is a more mature technology. Programming paradigm. + [Table of Content](./toc.md) | [Previous Chapter](./04-containers.md) | [Next Chapter: Regular Expression](./06-regex.md) ## Further Readings +- [Why does C++11 have `make_shared` but not `make_unique`](http://stackoverflow.com/questions/12580432/why-does-c11-have-make-shared-but-not-make-unique) + ## Licenses Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). \ No newline at end of file diff --git a/book/en-us/toc.md b/book/en-us/toc.md index 29c6138..0e2b535 100644 --- a/book/en-us/toc.md +++ b/book/en-us/toc.md @@ -68,6 +68,7 @@ + 5.1 RAII and reference counting + 5.2 `std::shared_ptr` + 5.3 `std::unique_ptr` + + 5.4 `std::weak_ptr` - [**Chapter 06 Regular Expression**](./06-regex.md) + 6.1 Introduction + Ordinary characters diff --git a/book/zh-cn/05-pointers.md b/book/zh-cn/05-pointers.md index 4be9619..0b3a66b 100644 --- a/book/zh-cn/05-pointers.md +++ b/book/zh-cn/05-pointers.md @@ -44,12 +44,12 @@ void foo(std::shared_ptr i) } int main() { - // auto pointer = new int(10); // 非法, 不允许直接赋值 - // 构造了一个 std::shared_ptr + // auto pointer = new int(10); // illegal, no direct assignment + // Constructed a std::shared_ptr auto pointer = std::make_shared(10); foo(pointer); std::cout << *pointer << std::endl; // 11 - // 离开作用域前,shared_ptr 会被析构,从而释放内存 + // The shared_ptr will be destructed before leaving the scope return 0; } ``` @@ -137,7 +137,7 @@ int main() { } ``` -## 5.4 std::weak_ptr +## 5.4 `std::weak_ptr` 如果你仔细思考 `std::shared_ptr` 就会发现依然存在着资源无法释放的问题。看下面这个例子: @@ -165,13 +165,13 @@ int main() { } ``` -运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 `a,b`,这使得 `a,b` 的引用计数均变为了 2,而离开作用域时,`a,b` 智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 `a,b` 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,如图所示: +运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 `a,b`,这使得 `a,b` 的引用计数均变为了 2,而离开作用域时,`a,b` 智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 `a,b` 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,如图 5.1: -![](../../assets/figures/pointers1.png) +![图 5.1](../../assets/figures/pointers1.png) -解决这个问题的办法就是使用弱引用指针 `std::weak_ptr`,`std::weak_ptr`是一种弱引用(相比较而言 `std::shared_ptr` 就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如下图所示: +解决这个问题的办法就是使用弱引用指针 `std::weak_ptr`,`std::weak_ptr`是一种弱引用(相比较而言 `std::shared_ptr` 就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如图 5.2 所示: -![](../../assets/figures/pointers2.png) +![图 5.2](../../assets/figures/pointers2.png) 在上图中,最后一步只剩下 B,而 B 并没有任何智能指针引用它,因此这块内存资源也会被释放。 diff --git a/book/zh-cn/toc.md b/book/zh-cn/toc.md index 32f168f..b7e9ae9 100644 --- a/book/zh-cn/toc.md +++ b/book/zh-cn/toc.md @@ -68,6 +68,7 @@ + 5.1 RAII 与引用计数 + 5.2 `std::shared_ptr` + 5.3 `std::unique_ptr` + + 5.4 `std::weak_ptr` - [**第 6 章 标准库: 正则表达式**](./06-regex.md) + 6.1 正则表达式简介 + 普通字符 diff --git a/code/5/5.1.cpp b/code/5/5.1.cpp deleted file mode 100644 index 9f2b0d0..0000000 --- a/code/5/5.1.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include - -void foo(std::shared_ptr i) -{ - (*i)++; -} - - -//struct Base { -// Base() { std::cout << " Base::Base()\n"; } -// ~Base() { std::cout << " Base::~Base()\n"; } -//}; -// -//struct Subclass: public Base { -// Subclass() { std::cout << " Subclass::Subclass()\n"; } -// ~Subclass() { std::cout << " Subclass::~Subclass()\n"; } -//}; -// -//void thr(std::shared_ptr p) -//{ -// std::this_thread::sleep_for(std::chrono::seconds(1)); -// std::shared_ptr lp = p; // 即使引用计数增加也是线程安全的 -// { -// static std::mutex io_mutex; -// std::lock_guard lk(io_mutex); -// std::cout << "local pointer in a thread:\n" -// << " lp.get() = " << lp.get() -// << ", lp.use_count() = " << lp.use_count() << '\n'; -// } -//} - -int main() -{ - // auto pointer = new int(10); // 非法, 不允许直接赋值 - // 构造了一个 std::shared_ptr - auto pointer = std::make_shared(10); - auto pointer2 = pointer; // 引用计数+1 - auto pointer3 = pointer; // 引用计数+1 - - - foo(pointer); - std::cout << *pointer << std::endl; // 11 - int *p = pointer.get(); // 这样不会增加引用计数 - - std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; - std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; - std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; - - pointer2.reset(); - std::cout << "reset pointer2:" << std::endl; - std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; - std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; - std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; - - pointer3.reset(); - std::cout << "reset pointer3:" << std::endl; - std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; - std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; - std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; - // std::cout << *pointer << std::endl; // 引用计数为0时, 非法访问 - - - // 离开作用域前,pointer 会被析构,引用计数减为0, 从而释放内存 - return 0; -} diff --git a/code/5/5.1.shared.ptr.a.cpp b/code/5/5.1.shared.ptr.a.cpp new file mode 100644 index 0000000..a467767 --- /dev/null +++ b/code/5/5.1.shared.ptr.a.cpp @@ -0,0 +1,52 @@ +// +// 5.1.shared.ptr.cpp +// chapter 05 start pointers and memory management +// modern c++ tutorial +// +// created by changkun at changkun.de +// https://github.com/changkun/modern-cpp-tutorial +// + +#include +#include + +void foo(std::shared_ptr i) +{ + (*i)++; +} + +int main() +{ + // auto pointer = new int(10); // illegal, no direct assignment + // std::shared_ptr construction + auto pointer = std::make_shared(10); + auto pointer2 = pointer; // reference count + 1 + auto pointer3 = pointer; // reference count + 1 + + + foo(pointer); + std::cout << *pointer << std::endl; // 11 + int *p = pointer.get(); // does not increase reference count + + std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; + std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; + std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; + + pointer2.reset(); + std::cout << "reset pointer2:" << std::endl; + std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; + std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; + std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; + + pointer3.reset(); + std::cout << "reset pointer3:" << std::endl; + std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; + std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; + std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; + // std::cout << *pointer << std::endl; // reference count equals 0, illegal access + + + // Before leaving the scope, the pointer is destructed and + // the reference count is reduced to 0 + return 0; +} diff --git a/code/5/5.2.cpp b/code/5/5.2.unique.ptr.cpp similarity index 56% rename from code/5/5.2.cpp rename to code/5/5.2.unique.ptr.cpp index 40166f7..2419c0e 100644 --- a/code/5/5.2.cpp +++ b/code/5/5.2.unique.ptr.cpp @@ -1,3 +1,12 @@ +// +// 5.2.unique.ptr.cpp +// chapter 05 start pointers and memory management +// modern c++ tutorial +// +// created by changkun at changkun.de +// https://github.com/changkun/modern-cpp-tutorial +// + #include #include @@ -14,28 +23,28 @@ void f(const Foo &) { int main() { std::unique_ptr p1(std::make_unique()); - // p1 不空, 输出 + // p1 is not empty, prints if (p1) p1->foo(); { std::unique_ptr p2(std::move(p1)); - // p2 不空, 输出 + // p2 is not empty, prints f(*p2); - // p2 不空, 输出 + // p2 is not empty, prints if(p2) p2->foo(); - // p1 为空, 无输出 + // p1 is empty, no prints if(p1) p1->foo(); p1 = std::move(p2); - // p2 为空, 无输出 + // p2 is empty, no prints if(p2) p2->foo(); - std::cout << "p2 被销毁" << std::endl; + std::cout << "p2 was destroied" << std::endl; } - // p1 不空, 输出 + // p1 is not empty, prints if (p1) p1->foo(); - // Foo 的实例会在离开作用域时被销毁 + // Foo instance will be destroied when leaving the scope } diff --git a/code/5/5.3.cpp b/code/5/5.3.weak.ptr.cpp similarity index 55% rename from code/5/5.3.cpp rename to code/5/5.3.weak.ptr.cpp index 3b80197..7bdda78 100644 --- a/code/5/5.3.cpp +++ b/code/5/5.3.weak.ptr.cpp @@ -1,3 +1,13 @@ +// +// 5.3.weak.ptr.cpp +// chapter 05 start pointers and memory management +// modern c++ tutorial +// +// created by changkun at changkun.de +// https://github.com/changkun/modern-cpp-tutorial +// + + #include #include @@ -8,14 +18,14 @@ class A { public: std::shared_ptr pointer; ~A() { - std::cout << "A 被销毁" << std::endl; + std::cout << "A was destroied" << std::endl; } }; class B { public: std::shared_ptr pointer; ~B() { - std::cout << "B 被销毁" << std::endl; + std::cout << "B was destroied" << std::endl; } }; int main() { diff --git a/code/5/Makefile b/code/5/Makefile new file mode 100644 index 0000000..642b9bc --- /dev/null +++ b/code/5/Makefile @@ -0,0 +1,14 @@ +# +# modern cpp tutorial +# +# created by changkun at changkun.de +# https://github.com/changkun/modern-cpp-tutorial +# + +all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) + +%.out: %.cpp Makefile + clang++ $< -o $@ -std=c++2a -pedantic + +clean: + rm *.out \ No newline at end of file