C++11 new feature learning - "Modern C++ Tutorial" reading notes: Chapters 4 and 5 - Containers, smart pointers and memory management

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::liststd::forward_listO(1)

​Unordered containers , compared with traditional C++ std::map, std::setthese containers introduce unsorted, , std::unordered_mapand two sets of unordered containers implemented by Hash. The average complexity of insertion and search is O ( 1 ) O(1)std::unordered_multimapstd::unordered_setstd::unordered_multisetO(1)

​Tuple , introduced std::tupleto 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::arraythe container, std::vectorthe 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::arrayThe 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_listThe usage of list container std::listis basically similar, the difference is:

std::listBased on a doubly linked list implementation, and std::forward_listusing 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::listis 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_setand std::unordered_multiset, where multiindicates whether the data type can contain equivalent elements.

4.3 Tuples

In traditional C++ containers, only std::paircan 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::tupleto store an unlimited number of different types of data.

Basic operations on tuples: build, get, unpack

  1. std::make_tuple: construct tuple

    auto student = std::make_tuple(3.8, 'A', "张三");
    // 或者
    std::tuple<double, char, std::string> student(3.8, 'A', "张三");
    
  2. std::get: Get the value of a position in the tuple

    cout << "姓名:" << 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;
    
  3. std::tie: tuple unpacking

    double 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_indexthe 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_catachieved

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_lenthe 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 deletemanually 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_sharedcreate, eliminate newexplicit 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_uniquecreation, prohibiting other smart pointers from sharing the same object with it to ensure code safety. Although it cannot be copied, std::moveobjects 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_ptrcircular 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 newand delete"remember" to release resources. C++11 introduces the concept of smart pointers based on the idea of ​​reference counting , including // , header std::shared_ptrfiles need to be included when using , so that programmers do not need to care about manually releasing memory.std::unique_ptrstd::weak_ptr<memory>

5.2 std::shared_ptr shared pointer

​Canshared_ptr record how many points to an object shared_ptrtogether , and automatically delete the object when the reference count becomes zero, avoiding deletethe explicit call of .

​ However, the use shared_ptrstill needs to be newcalled std::make_sharedto eliminate the explicit use of and automatically return a pointer .newstd::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_ptrProvides methods for obtaining raw pointers get(), decrementing reference counts reset(), viewing reference counts, and more. use_count()Note that here resetis shared_ptrexecuted 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_ptrIt 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::movebe 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_ptrstill 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 Aand B, Aand Beach 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_ptrThere is no *and ->operator, so resources cannot be manipulated. You can:

① Use its expired()method to check weak_ptrwhether 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.

Guess you like

Origin blog.csdn.net/lj164567487/article/details/126861393