C++11 study notes (3) - general tools (on) (including important features smart pointer Smart pointer)

1.Pair

In C++11, std::pair is a template class used to combine two values ​​into a single unit. It can pair two values ​​of different types together, and provides access and manipulation of the pair of values.

The definition of std::pair

template<class T1, class T2>
struct pair{
    
    
	T1 first;
	T2 second;
};

some usage

Create and initialize:

A std::pair object can be created and initialized using a constructor or a braced initializer list. For example:

std::pair<int, std::string> myPair(42, "Hello");
std::pair<double, bool> anotherPair = {
    
    3.14, true};

visiting member

The members of the std::pair object can be accessed through .first and .second. For example:

std::pair<int, std::string> myPair(42, "Hello");
int x = myPair.first;
std::string str = myPair.second;

compare and sort

std::pair can perform comparison operations, and compare according to the values ​​of .first and .second. std::pair objects can be sorted in the container. For example:

std::pair<int, std::string> pair1(42, "Hello");
std::pair<int, std::string> pair2(10, "World");

bool result = (pair1 < pair2);  // 比较操作
std::vector<std::pair<int, std::string>> myVector = {
    
    pair1, pair2};
std::sort(myVector.begin(), myVector.end());  // 容器排序

Example of use:

std::pair is often used with functions that return multiple values, and in cases where two values ​​need to be passed as a single unit. For example:

std::pair<int, std::string> getPerson() {
    
    
    int age = 25;
    std::string name = "John";
    return std::make_pair(age, name);
}

std::pair<int, int> divideAndRemainder(int dividend, int divisor) {
    
    
    int quotient = dividend / divisor;
    int remainder = dividend % divisor;
    return {
    
    quotient, remainder};
}

make_pair()

A pair object can be generated without writing out the type, for example:

std::pair<int, char> myPair(42, "Hello");
std::make_pair(42, "Hello");

operation function

insert image description here

std::pair provides a convenient way to combine two values, and can be used in a variety of scenarios. It is one of the tools commonly used in C++ to simplify code and improve code readability.

2.Tuple

Tuple extends the concept of pair to have any number of elements, each of which type can be specified.

Definition of Tuple

template<typename... Types>
class tuple;

Tuple example

#include <iostream>
#include <tuple>

int main() {
    
    
    // 创建一个包含整数、字符串和浮点数的元组
    std::tuple<int, std::string, double> myTuple(42, "Hello", 3.14);

    // 访问元组中的元素
    int intValue = std::get<0>(myTuple);
    std::string stringValue = std::get<1>(myTuple);
    double doubleValue = std::get<2>(myTuple);

    // 修改元组中的元素
    std::get<0>(myTuple) = 100;

    // 使用tie函数将元组的元素解包到变量中
    
    int a;
    std::string b;
    double c;
    std::tie(a, b, c) = myTuple;
		//std::tie(a, std::ignore, c) = myTuple;忽略某些元素
    // 打印元组的元素
    std::cout << "Tuple elements: " << a << ", " << b << ", " << c << std::endl;
		//c++11也可直接输出myTuple
    return 0;
}

operation function

insert image description here

std::tuple_size

Used to get the size of a std::tuple.

#include <iostream>
#include <tuple>

int main() {
    
    
    std::tuple<int, std::string, double> myTuple;

    std::cout << "Tuple size: " << std::tuple_size<decltype(myTuple)>::value << std::endl;

    return 0;
}

The output result is: Tuple size: 3, indicating that there are three elements in std::tuple.

std::tuple_element

Used to get the element type at the specified position in the std::tuple.


#include <iostream>
#include <tuple>

int main() {
    
    
    using MyTuple = std::tuple<int, std::string, double>;

    std::tuple_element<1, MyTuple>::type myElement;

    std::cout << "Element type: " << typeid(myElement).name() << std::endl;

    return 0;
}

The output result is: Element type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE, indicating that the second element type of std::tuple is std::string.

std::tuple_cat

Used to combine multiple std::tuples into one large std::tuple.


#include <iostream>
#include <tuple>

int main() {
    
    
    std::tuple<int, std::string> tuple1(42, "Hello");
    std::tuple<double> tuple2(3.14);

    auto combinedTuple = std::tuple_cat(tuple1, tuple2);

    std::cout << "Combined tuple size: " << std::tuple_size<decltype(combinedTuple)>::value << std::endl;

    return 0;
}

The output result is: Combined tuple size: 3, which means that after combining tuple1 and tuple2, a std::tuple containing three elements is obtained.

pair and tuple

Conversion from std::pair to std::tuple

A std::pair can be converted to a std::tuple using the std::make_tuple function.

#include <iostream>
#include <tuple>

int main() {
    
    
    std::pair<int, double> myPair(42, 3.14);

    std::tuple<int, double> myTuple = std::make_tuple(myPair.first, myPair.second);

    std::cout << "Tuple elements: " << std::get<0>(myTuple) << ", " << std::get<1>(myTuple) << std::endl;

    return 0;
}

In the above example, we have an object myPair of type std::pair<int, double>, we can use std::make_tuple to convert it to object myTuple of type std::tuple<int, double>.

Conversion from std::tuple to std::pair

You can use the std::get function to extract the elements of a std::tuple and use these elements to create a std::pair.


#include <iostream>
#include <tuple>

int main() {
    
    
    std::tuple<int, double> myTuple(42, 3.14);

    std::pair<int, double> myPair = std::make_pair(std::get<0>(myTuple), std::get<1>(myTuple));

    std::cout << "Pair elements: " << myPair.first << ", " << myPair.second << std::endl;

    return 0;
}

In the above example, we have an object myTuple of type std::tuple<int, double>, we can use the std::get function to extract its elements, and use these elements to create an object myPair of type std::pair<int, double>.

3.Smart pointer***

Pointers are an important feature of c/c++, but problems such as dangling, multiple deletions, and resource leaks often occur during use. A common way to avoid these problems is to use smart pointers.
Since C++11, the standard library provides two types of smart pointers: shared_ptr and unique_ptr, while auto_ptr is deprecated, they are defined in <memory>

shared_ptr

std::shared_ptr, as a type of smart pointer, is used to manage dynamically allocated memory more safely and conveniently. std::shared_ptr uses reference counting to track the owner of the resource and automatically releases the resource when it is no longer needed.

The following are the basic usage and examples of std::shared_ptr:

Create a std::shared_ptr object:

A std::shared_ptr object can be created using the std::make_shared function or directly using the std::shared_ptr constructor.

#include <iostream>
#include <memory>

int main() {
    
    
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::shared_ptr<int> ptr2(new int(100));

    std::cout << *ptr1 << std::endl; // 输出:42
    std::cout << *ptr2 << std::endl; // 输出:100

    return 0;
}

In the above example, we use std::make_shared to create a std::shared_ptr object ptr1 that contains an integer value, and use the constructor of std::shared_ptr to create another std::shared_ptr object ptr2.

Shared owned resources:

One std::shared_ptr can be assigned to another std::shared_ptr so that they share ownership of the same resource.

#include <iostream>
#include <memory>

int main() {
    
    
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::shared_ptr<int> ptr2 = ptr1;

    std::cout << *ptr1 << std::endl; // 输出:42
    std::cout << *ptr2 << std::endl; // 输出:42

    return 0;
}

In the above example, we assigned ptr1 to ptr2 and they both now point to the same integer resource and share ownership of that resource.

Reference counting and resource release:

std::shared_ptr uses reference counting to track the number of owners of a resource. When the last std::shared_ptr is destructed or assigned a new value, the reference count is decremented and a check is made to see if the resource needs to be freed.

#include <iostream>
#include <memory>

int main() {
    
    
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::shared_ptr<int> ptr2 = ptr1;
    std::shared_ptr<int> ptr3 = ptr1;

    std::cout << *ptr1 << std::endl; // 输出:42
    std::cout << *ptr2 << std::endl; // 输出:42
    std::cout << *ptr3 << std::endl; // 输出:42

    ptr1.reset();

    std::cout << std::boolalpha;
    std::cout << "ptr1 is nullptr: " << (ptr1 == nullptr) << std::endl; // 输出:true
    std::cout << "ptr2 is nullptr: " << (ptr2 == nullptr) << std::endl; // 输出:false
    std::cout << "ptr3 is nullptr: " << (ptr3 == nullptr) << std::endl; // 输出:false

    return 0;
}

In the above example, we created three std::shared_ptr objects ptr1, ptr2 and ptr3, which all point to the same integer resource. When ptr1 calls the reset function, it no longer owns the resource, the reference count decreases and the resource is released, but ptr2 and ptr3 are still valid and own the resource.

std::shared_ptr also provides other useful member functions, such as get (returns the underlying pointer), use_count (returns the reference count), unique (checks whether it is the only owner), etc.

Using std::shared_ptr can effectively avoid problems such as memory leaks and dangling pointers, and provide a convenient resource management mechanism. However, be careful to avoid circular reference problems, as it may cause resources not to be freed.

destructor

The destructor of std::shared_ptr is executed by the deleter specified by its template parameter. Destruction behavior can be customized by providing a custom deleter.

A deleter is a callable object that performs the deallocation of resources when the reference count of a std::shared_ptr reaches zero. A deleter can be a function pointer, a function object, a lambda expression, or an object of a custom type.

Here's an example using a custom deleter:

#include <iostream>
#include <memory>

// 自定义删除器
struct CustomDeleter {
    
    
    void operator()(int* ptr) {
    
    
        std::cout << "Custom deleter is called" << std::endl;
        delete ptr;
    }
};

int main() {
    
    
    std::shared_ptr<int> ptr(new int(42), CustomDeleter());

    return 0;
}

In the above example, we define a structure called CustomDeleter, which overloads the parenthesis operator to implement custom deletion behavior. In the main function, we use the constructor of std::shared_ptr to create a std::shared_ptr object ptr, and pass the custom deleter as a parameter.

When the reference count of std::shared_ptr is zeroed, the parenthesis operator of the custom deleter is called to release the resource and perform the custom deletion behavior we defined. In this example, the custom deleter prints a message and deletes the pointer to the integer.

Note that when providing a custom deleter, you need to ensure that the deleter is compatible with the pointer type and follows proper resource release rules. By customizing the deleter, you can achieve more flexible resource management and destruction behavior to meet specific needs.

other operations

insert image description hereinsert image description here

weak_ptr

C++11 introduces std::weak_ptr as a smart pointer type to solve the circular reference problem that std::shared_ptr may cause. std::weak_ptr allows weak references to objects managed by std::shared_ptr without increasing the reference count and preventing the object from being destroyed.

The following are the basic usage and examples of std::weak_ptr:

Create std::weak_ptr object

A std::weak_ptr object can be created by casting the std::shared_ptr object to a std::weak_ptr.

#include <iostream>
#include <memory>

int main() {
    
    
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
    std::weak_ptr<int> weakPtr = sharedPtr;

    // 输出:42
    if (auto lockedPtr = weakPtr.lock()) {
    
    
        std::cout << *lockedPtr << std::endl;
    } else {
    
    
        std::cout << "Resource has been released" << std::endl;
    }

    return 0;
}

In the above example, we created a std::shared_ptr object sharedPtr to manage an integer resource and created a std::weak_ptr object weakPtr by converting it to std::weak_ptr. Note that casting to std::weak_ptr does not increase the resource's reference count.

Check if std::weak_ptr is valid and access resource

The lock() function can be used to check whether a std::weak_ptr is valid and to gain shared access to the resource.

#include <iostream>
#include <memory>

int main() {
    
    
    std::weak_ptr<int> weakPtr;

    {
    
    
        std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
        weakPtr = sharedPtr;

        // 输出:42
        if (auto lockedPtr = weakPtr.lock()) {
    
    
            std::cout << *lockedPtr << std::endl;
        } else {
    
    
            std::cout << "Resource has been released" << std::endl;
        }
    }

    // 输出:"Resource has been released"
    if (auto lockedPtr = weakPtr.lock()) {
    
    
        std::cout << *lockedPtr << std::endl;
    } else {
    
    
        std::cout << "Resource has been released" << std::endl;
    }

    return 0;
}

In the above example, we created a std::shared_ptr object sharedPtr in scope and converted it to a std::weak_ptr object weakPtr. After the scope ends, the sharedPtr is destroyed and the resource is freed. In the subsequent code, we check whether the weakPtr is valid and access the resource by calling the lock() function. If weakPtr is valid, the lock() function will return a valid std::shared_ptr object, otherwise it will return a null pointer.

view resources

expired()

Used to check whether std::weak_ptr has expired (that is, whether the resource pointed to has been released). Returns true if the resource has been released; otherwise returns false.

use_count()

The number of valid std::shared_ptr objects used to obtain the same resource as std::weak_ptr.

#include <iostream>
#include <memory>

int main() {
    
    
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
    std::weak_ptr<int> weakPtr = sharedPtr;

    // 输出:1
    std::cout << "use_count: " << sharedPtr.use_count() << std::endl;

    if (weakPtr.expired()) {
    
    
        std::cout << "Resource has been released" << std::endl;
    } else {
    
    
        // 输出:42
        std::cout << "Value: " << *weakPtr.lock() << std::endl;
    }

    sharedPtr.reset();

    // 输出:0
    std::cout << "use_count: " << weakPtr.use_count() << std::endl;

    if (weakPtr.expired()) {
    
    
        std::cout << "Resource has been released" << std::endl;
    } else {
    
    
        std::cout << "Value: " << *weakPtr.lock() << std::endl;
    }

    return 0;
}

In the above example, we created a std::shared_ptr object sharedPtr to manage an integer resource and created a weak reference by converting it to a std::weak_ptr object weakPtr. We use the use_count() function to get the number of valid std::shared_ptr objects sharing the same resource as sharedPtr. In the if statement, we use the expired() function to check whether the weakPtr has expired, and then use the lock() function to get a valid std::shared_ptr object and output its value. In the subsequent code, we call the reset() function to empty the sharedPtr, release resources, and check whether the weakPtr has expired.

It should be noted that since std::weak_ptr does not increase the reference count, calling the use_count() function returns the number of valid std::shared_ptr objects that share resources with it.

other operations

insert image description here

By using std::weak_ptr, we can avoid the circular reference problem that std::shared_ptr may cause, and manage the life cycle of resources more flexibly.

unique_ptr

std::unique_ptr is a smart pointer with exclusive ownership, which means that only one std::unique_ptr can own the object pointed to by the pointer at a time. When unique_ptr is destroyed, the object it points to will be destroyed automatically.

The following are the basic usage and examples of std::unique_ptr:

Create std::unique_ptr object

A std::unique_ptr object can be created using the std::make_unique function or directly using the std::unique_ptr constructor.

#include <iostream>
#include <memory>

int main() {
    
    
    std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(42);
    std::unique_ptr<int> uniquePtr2(new int(100));

    // 输出:42
    std::cout << *uniquePtr1 << std::endl;

    // 输出:100
    std::cout << *uniquePtr2 << std::endl;

    return 0;
}

In the above example, we use the std::make_unique function and constructor to create two std::unique_ptr objects uniquePtr1 and uniquePtr2 to manage two integer resources. Note that std::make_unique is a function introduced by C++14. If you are using C++11, you can directly use the constructor of std::unique_ptr.

Execute the transfer of ownership

std::unique_ptr has the feature of exclusive ownership, and ownership can be transferred from one std::unique_ptr to another std::unique_ptr through move semantics.

#include <iostream>
#include <memory>

int main() {
    
    
    std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(42);
    std::unique_ptr<int> uniquePtr2;

    uniquePtr2 = std::move(uniquePtr1);

    // 输出:42
    std::cout << *uniquePtr2 << std::endl;

    return 0;
}

In the above example, we transferred the ownership of uniquePtr1 to uniquePtr2 by using std::move. This way, uniquePtr2 now owns the original resource, and uniquePtr1 no longer owns the resource.

Use a custom deleter

可以通过提供自定义删除器来指定std::unique_ptr在释放资源时的行为。删除器是一个函数对象或函数指针,用于定义资源释放的方式。
#include <iostream>
#include <memory>

struct CustomDeleter {
    
    
    void operator()(int* ptr) {
    
    
        std::cout << "Deleting resource: " << *ptr << std::endl;
        delete ptr;
    }
};

int main() {
    
    
    std::unique_ptr<int, CustomDeleter> uniquePtr(new int(42));

    // 输出:42
    std::cout << *uniquePtr << std::endl;

    return 0;
}

In the above example, we created a std::unique_ptr object uniquePtr with a custom deleter CustomDeleter. When the uniquePtr is destroyed, the custom deleter will be called and is responsible for freeing the resource.

std::unique_ptr provides a lightweight smart pointer, suitable for managing objects with a single ownership, providing efficient memory management and safe resource release.

about array

C++ needs to use delete[] to delete an array. Since C++ cannot distinguish whether a pointer points to a single object or an array, an error will occur when the pointer is automatically deleted. The standard library provides a special version for this.
In C++11, the array version of std::unique_ptr is introduced, namely std::unique_ptr<T[]>, which is used to manage dynamically allocated array objects.

Here is an example of std::unique_ptr managing a dynamic array:

#include <iostream>
#include <memory>

int main() {
    
    
    std::unique_ptr<int[]> uniquePtr(new int[5]);

    for (int i = 0; i < 5; i++) {
    
    
        uniquePtr[i] = i + 1;
    }

    for (int i = 0; i < 5; i++) {
    
    
        std::cout << uniquePtr[i] << " ";
    }

    // 输出:1 2 3 4 5
    std::cout << std::endl;

    return 0;
}

In the above example, we use std::unique_ptr<int[]> to create a std::unique_ptr object uniquePtr to manage a dynamic array containing 5 integers. By using the array subscript operator [], we can access and modify elements in the array. When uniquePtr is destroyed, it will automatically release the memory occupied by the dynamic array.

It should be noted that std::unique_ptr<T[]> is only suitable for managing arrays dynamically allocated through new T[], rather than pointing to existing arrays. This version does not provide operators '*' and '->'. When using std::unique_ptr<T[]>, there is no need to manually call delete[] to release memory, std::unique_ptr will automatically handle the release of memory. Additionally, this version does not support direct conversions between different types, and does not allow pointers to derived element types.

other operations

insert image description here

auto_ptr

std::auto_ptr is a smart pointer introduced by the C++98 standard for managing dynamically allocated objects. However, std::auto_ptr has been deprecated since C++11 and is not recommended for new code because of some bugs and unsafe behavior.
Here are some important notes about std::auto_ptr:

transfer of ownership

Unlike std::unique_ptr, std::auto_ptr supports the transfer of ownership, that is, the ownership of resources can be transferred from one std::auto_ptr to another std::auto_ptr. This means that the original std::auto_ptr will no longer own the resource after transferring ownership.

Arrays are not supported

std::auto_ptr is only suitable for managing single objects, and does not support managing dynamically allocated arrays.

Deleter Limitations

std::auto_ptr only supports the use of the default deleter, and cannot customize the deleter. This means that when a std::auto_ptr is destroyed, only delete is called to release the resource.

unsafe copy semantics

There is a problem with the copy semantics of std::auto_ptr, which uses move semantics instead of traditional copy semantics. This causes the original std::auto_ptr to lose ownership of the resource after copying, possibly resulting in repeated releases of the resource.

Therefore, it is recommended to use the safer and more powerful smart pointer types introduced by C++11, such as std::unique_ptr for cases of exclusive ownership, std::shared_ptr for cases of shared ownership, and std::weak_ptr for solving circular reference problems. These smart pointer types provide better semantics and stronger type checking, enable better resource management and provide better memory safety.

Replenish

memory overhead

Compared with raw pointers, smart pointers usually introduce certain memory overhead. Smart pointer objects usually contain additional data members such as reference counts, which may take up more memory. Although this overhead is negligible in most cases, for special environments such as embedded systems with very limited resources, the use of smart pointers needs to be carefully considered.

not suitable for some situations

Smart pointers are not a general solution for all situations. In some specific application scenarios, such as interacting with the C interface, processing external resources, etc., it may be necessary to manually manage resources, and it is not suitable to use smart pointers.

Semantic differences and caveats

Different types of smart pointers have different semantics and behaviors, requiring understanding and attention to how they are used. For example, the shared ownership of std::shared_ptr may bring additional overhead and thread safety issues, while std::unique_ptr is suitable for exclusive ownership scenarios. In addition, the use of smart pointers also needs to pay attention to issues such as circular references, null pointer checks, and potential performance impacts.

While smart pointers have their flaws and caveats, they provide a convenient, safe, and reliable way of managing resources in most cases. When using smart pointers, you need to understand their semantics and behavior, and choose and use them in combination with specific application scenarios.

Guess you like

Origin blog.csdn.net/qq_44616044/article/details/131184594