Online reading link: https://changkun.de/modern-cpp/zh-cn/00-preface/
I am currently reading the "Modern C++ Tutorial" on the link, and post it as a study note. If there is any infringement, it will be deleted immediately .
Chapter 4 Containers
After C++11, the provided containers have been expanded, including linear containers (ordered containers), unordered containers and tuples , enabling C++ to implement some functions more conveniently and efficiently, but there are still some containers with very limited functions , you need to manually add some codes to achieve more complete functions.
Linear container , ① introduces the container, which is a typestd::array
with a fixed size after instantiating the object ; ② introduces a type similar to but based on a one-way linked list, and the insertion and search complexity is O ( 1 ) O(1)std::list
std::forward_list
O(1) 。
Unordered containers , compared with traditional C++ std::map
, std::set
these containers introduce unsorted, , std::unordered_map
and two sets of unordered containers implemented by Hash. The average complexity of insertion and search is O ( 1 ) O(1)std::unordered_multimap
std::unordered_set
std::unordered_multiset
O(1) 。
Tuple , introduced std::tuple
to store an unlimited number of different types of data , provides basic operations such as construction, acquisition, unpacking, and merging . However, there is no ready-made library function implementation for runtime indexing and tuple traversal , but It can be realized through some common template function codes, see the detailed introduction later.
4.1 Linear Containers
std::array
Introduce std::array
the container, std::vector
the difference is that the size of the former object is fixed, you must use a constant expression to define the size of the array, while the latter can be automatically expanded, and will not automatically return the memory corresponding to the deleted element, you need to manually run to shrink_to_fit()
release .
constexpr int len = 4;
std::array<int, len> arr = {
1, 2, 3, 4};
std::array
The variable name cannot be implicitly converted to a pointer to the element type , but needs to be passed some other way:
void foo(int *p, int len) {
return;
}
std::array<int, 4> arr = {
1,2,3,4};
// C 风格接口传参
// foo(arr, arr.size()); // 非法, 无法隐式转换
foo(&arr[0], arr.size());
foo(arr.data(), arr.size());
【std::forward_list】
std::forward_list
The usage of list container std::list
is basically similar, the difference is:
std::list
Based on a doubly linked list implementation, and std::forward_list
using a singly linked list implementation, providing O ( 1 ) O(1)Element insertion with O ( 1 ) complexity, does not support random access, does not provide size()
the methodstd::list
is more efficient than the space utilization when bidirectional iteration is not required.
4.2 Unordered containers
There are ordered containersstd::map
/ in traditional C++ std::set
. These types internally compare and store elements through red-black trees. The average complexity of insertion and search is O ( log ( N ) ) O(\log(N))O ( log ( N )) , traverse the container one by <
one in order .
**无序容器**的元素不进行排序,而是通过Hash表实现,插入和搜索的平均复杂度为 $O(1)$ ,性能显著提升。
C++ introduces two sets of unordered containers: std::unordered_map
, std::unordered_multimap
, std::unordered_set
and std::unordered_multiset
, where multi
indicates whether the data type can contain equivalent elements.
4.3 Tuples
In traditional C++ containers, only std::pair
can be used to store different types of data, but only two elements can be stored. From this, the tuple type in python is thought of, and is introduced std::tuple
to store an unlimited number of different types of data.
Basic operations on tuples: build, get, unpack
-
std::make_tuple
: construct tupleauto student = std::make_tuple(3.8, 'A', "张三"); // 或者 std::tuple<double, char, std::string> student(3.8, 'A', "张三");
-
std::get
: Get the value of a position in the tuplecout << "姓名:" << std::get<2>(student) << endl; // C++14中还增加了使用类型来获取元组中的对象 std::tuple<std::string, double, double, int> t("123", 4.5, 6.7, 8); std::cout << std::get<std::string>(t) << std::endl; std::cout << std::get<double>(t) << std::endl; // 非法, 引发编译期错误 std::cout << std::get<3>(t) << std::endl;
-
std::tie
: tuple unpackingdouble gpa; char grade; std::string name; std::tie(gpa, grade, name) = student;
case procedure
#include <tuple>
#include <iostream>
auto get_student(int id)
{
// 返回类型被推断为 std::tuple<double, char, std::string>
if (id == 0) return std::make_tuple(3.8, 'A', "张三");
if (id == 1) return std::make_tuple(2.9, 'C', "李四");
if (id == 2) return std::make_tuple(1.7, 'D', "王五");
return std::make_tuple(0.0, 'D', "null");
// 如果只写 0 会出现推断错误, 编译失败
}
int main()
{
auto student = get_student(0);
std::cout << "ID: 0, " << "GPA: " << std::get<0>(student) << ", "
<< "成绩: " << std::get<1>(student) << ", "
<< "姓名: " << std::get<2>(student) << '\n';
double gpa;
char grade;
std::string name;
// 元组进行拆包
std::tie(gpa, grade, name) = get_student(1);
std::cout << "ID: 1, " << "GPA: " << std::get<0>(student) << ", "
<< "成绩: " << std::get<1>(student) << ", "
<< "姓名: " << std::get<2>(student) << '\n';
}
runtime index
std::get<>
Relying on a compile-time constant, such as the following code is illegal:
int index = 1;
std::get<index>(t); // 非法
So C++17 was introduced std::varient
, providing varient<>
type template parameters
int i = 1;
std::cout << tuple_index(t, i) << std::endl;
Note that you need to add the following code first to use tuple_index
the function
#include <variant>
template <size_t n, typename... T>
constexpr std::variant<T...> _tuple_index(const std::tuple<T...>& tpl, size_t i) {
if constexpr (n >= sizeof...(T))
throw std::out_of_range("越界.");
if (i == n)
return std::variant<T...>{
std::in_place_index<n>, std::get<n>(tpl) };
return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i);
}
template <typename... T>
constexpr std::variant<T...> tuple_index(const std::tuple<T...>& tpl, size_t i) {
return _tuple_index<0>(tpl, i);
}
template <typename T0, typename ... Ts>
std::ostream & operator<< (std::ostream & s, std::variant<T0, Ts...> const & v) {
std::visit([&](auto && x){
s << x;}, v);
return s;
}
Tuple merge and traversal
Merging two tuples is std::tuple_cat
achieved
auto new_tuple = std::tuple_cat(get_student(1), std::move(t));
While traversing a tuple, you can first get the length of the tuple, and then index the tuple to get the elements
template <typename T>
auto tuple_len(T &tpl) {
return std::tuple_size<T>::value;
}
for(int i = 0; i != tuple_len(new_tuple); ++i)
std::cout << tuple_index(new_tuple, i) << std::endl; // 运行期索引
Note that you need to call the following code before you can call tuple_len
the function
template <typename T>
auto tuple_len(T &tpl) {
return std::tuple_size<T>::value;
}
Chapter 5 Smart Pointers and Memory Management
Generally speaking, objects apply for space when they are constructed, and release space when they leave the scope or when they are destructed, but objects created through dynamic allocationnew
need to be used and delete
manually managed in traditional C++ to create and delete them, but an object is created After coming out, it is likely to be pointed by multiple pointers, so it is difficult to determine when to operate delete
.
As a result, C++11 introduced the concept of smart pointers and adopted the method of reference counting .
Shared pointersshared_ptr
, use std::make_shared
create, eliminate new
explicit references to the object, and when the number of shared pointers pointing to the same object becomes zero, the object will be automatically deleted; in addition, it also provides operations such as obtaining original pointers, reducing reference counts, and viewing reference counts method.
Exclusive pointerunique_ptr
, using std::make_unique
creation, prohibiting other smart pointers from sharing the same object with it to ensure code safety. Although it cannot be copied, std::move
objects can be transferred to other smart pointers through move semantics. During the transfer process, no class construction and analysis will occur. structure.
Weak pointersweak_ptr
are used to solve the problem of memory leaks that may be caused by shared_ptr
circular references.
Introduction: About Reference Counting
Reference counting is a count generated to prevent memory leaks .
The basic idea is: for dynamically allocated objects, ① every time a reference to the same object is added, the reference count of the referenced object is increased; ② every time a reference is deleted, the reference count will be reduced by one ; When the count decreases to zero , the heap memory it points to is automatically deleted .
In the actual practice of traditional C++, it is likely to forget to release resources and cause leaks. The usual practice is: (for an object) apply for space during the constructor, and release the space when the destructor/leaves the function scope . This is the RAII resource acquisition and initialization technology .
In traditional C++, if you allocate space to an object on free storage, you need to use new
and delete
"remember" to release resources. C++11 introduces the concept of smart pointers based on the idea of reference counting , including // , header std::shared_ptr
files need to be included when using , so that programmers do not need to care about manually releasing memory.std::unique_ptr
std::weak_ptr
<memory>
5.2 std::shared_ptr shared pointer
Canshared_ptr
record how many points to an object shared_ptr
together , and automatically delete the object when the reference count becomes zero, avoiding delete
the explicit call of .
However, the use shared_ptr
still needs to be new
called std::make_shared
to eliminate the explicit use of and automatically return a pointer .new
std::shared_ptr
#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i) {
(*i)++;
}
int main() {
// auto pointer = new int(10); // illegal, no direct assignment
// Constructed a std::shared_ptr
auto pointer = std::make_shared<int>(10);
foo(pointer);
std::cout << *pointer << std::endl; // 11
// The shared_ptr will be destructed before leaving the scope
return 0;
}
std::shared_ptr
Provides methods for obtaining raw pointers get()
, decrementing reference counts reset()
, viewing reference counts, and more. use_count()
Note that here reset
is shared_ptr
executed by a specific , after execution, the smart pointer cannot obtain the original pointer and value , and the reference count of other smart pointers pointing to the same pointer is reduced by one, and the original pointer can be obtained and accessed at will.
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
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; // pointer2 has been reset; 0
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; // pointer3 has been reset; 0
5.3 std::unique_ptr exclusive pointer
std::unique_ptr
It is an exclusive smart pointer that prohibits other smart pointers from sharing the same object with it to ensure code security. The creation method is similar to the previous shared pointer, yes, but this method was added in std::make_unique<>()
C++14 (C++ 11 o'clock), you can use the following code to implement it yourself.
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
Self-fulfillmentstd::make_unique
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
Although exclusive pointers cannot be copied, they can std::move
be transferred unique_ptr
.
#include <iostream>
#include <memory>
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<Foo> p1(std::make_unique<Foo>());
if (p1) p1->foo(); // p1 不空, 输出
{
std::unique_ptr<Foo> p2(std::move(p1)); // 此时不会再调用构造函数
f(*p2); // p2 不空, 输出
if(p2) p2->foo();// p2 不空, 输出
if(p1) p1->foo();// p1 为空, 无输出
p1 = std::move(p2);
if(p2) p2->foo(); // p2 为空, 无输出
std::cout << "p2 被销毁" << std::endl; // p2本身已经是空指针,因此不会调用析构函数
}
if (p1) p1->foo(); // p1 不空, 输出
// Foo 的实例会在离开作用域时被销毁
}
5.4 [std::weak_ptr weak pointer]
Think carefully, there is std::shared_ptr
still a problem that resources cannot be released, as shown in the following code
struct A;
struct B;
struct A {
std::shared_ptr<B> pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
struct B {
std::shared_ptr<A> pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
}
The smart pointers created in the main function point to A
and B
, A
and B
each of and has a shared pointer pointing to each other. Therefore, the reference numbers of the shared pointers of andA
are both 2. When the main function finishes running, only one shared pointer is destroyed respectively, referencing The number is not cleared , and the outside can no longer find this area, resulting in a memory leak , the process is shown in the following figure:B
Weak references will not increase the reference count. std::weak_ptr
There is no *
and ->
operator, so resources cannot be manipulated. You can:
① Use its expired()
method to check weak_ptr
whether the shared pointer exists.
② Use its lock()
method to return a pointer to the original object when the original object is not released std::shared_ptr
, and then access the original object resource. If there is no shared pointer pointing to the original object resource, return nullptr
.