C++11学习笔记(3)——通用工具(上)(包含重要特性智能指针Smart pointer)

1.Pair

在C++11中,std::pair是一个模板类,用于将两个值组合成一个单元。它可以将两个不同的类型的值配对在一起,并且提供了对这对值的访问和操作。

std::pair的定义

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

一些用法

创建和初始化:

可以使用构造函数或花括号初始化列表来创建和初始化std::pair对象。例如:

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

访问成员

std::pair对象的成员可以通过.first和.second进行访问。例如:

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

比较和排序

std::pair可以进行比较操作,根据.first和.second的值进行比较。std::pair对象可以在容器中进行排序。例如:

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());  // 容器排序

使用范例:

std::pair常常用于返回多个值的函数,以及在需要将两个值作为单个单元传递的情况下。例如:

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()

无需写出类型就能生成一个pair对象,例如:

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

操作函数

在这里插入图片描述

std::pair提供了一种便捷的方式来组合两个值,并且可以在多种场景下使用。它是C++中常用的工具之一,用于简化代码和提高代码的可读性。

2.Tuple

Tuple扩展了pair的概念,拥有任意数量的元素,其中每个类型都可以被指定。

Tuple的定义

template<typename... Types>
class tuple;

Tuple示例

#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;
}

操作函数

在这里插入图片描述

std::tuple_size

用于获取std::tuple的大小。

扫描二维码关注公众号,回复: 15793877 查看本文章
#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;
}

输出结果为:Tuple size: 3,表示std::tuple中有三个元素。

std::tuple_element

用于获取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;
}

输出结果为:Element type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE,表示std::tuple的第二个元素类型为std::string。

std::tuple_cat

用于将多个std::tuple合并成一个大的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;
}

输出结果为:Combined tuple size: 3,表示将tuple1和tuple2合并后,得到了一个包含三个元素的std::tuple。

pair与tuple

从std::pair到std::tuple的转换

可以使用std::make_tuple函数将std::pair转换为std::tuple。

#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;
}

在上述示例中,我们有一个std::pair<int, double>类型的对象myPair,我们可以使用std::make_tuple将其转换为std::tuple<int, double>类型的对象myTuple。

从std::tuple到std::pair的转换

可以使用std::get函数将std::tuple的元素提取出来,并使用这些元素创建一个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;
}

在上述示例中,我们有一个std::tuple<int, double>类型的对象myTuple,我们可以使用std::get函数将其元素提取出来,并使用这些元素创建一个std::pair<int, double>类型的对象myPair。

3.Smart pointer智能指针***

指针是c/c++的重要特性,但使用中常常会出现空悬,多次删除,资源泄露等问题,避免这些问题的一个通常做法是使用智能指针。
自C++11起,标准库提供两大类智能指针:shared_ptr和unique_ptr,而auto_ptr被弃用,它们定义在< memory >内

shared_ptr

std::shared_ptr作为智能指针的一种类型,用于更安全和方便地管理动态分配的内存。std::shared_ptr使用引用计数的方式来跟踪资源的所有者,并在不再需要时自动释放资源。

以下是std::shared_ptr的基本用法和示例:

创建std::shared_ptr对象:

可以使用std::make_shared函数或直接使用std::shared_ptr的构造函数来创建std::shared_ptr对象。

#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;
}

在上述示例中,我们使用std::make_shared创建了一个包含整数值的std::shared_ptr对象ptr1,以及使用std::shared_ptr的构造函数创建了另一个std::shared_ptr对象ptr2。

共享拥有资源:

可以将一个std::shared_ptr赋值给另一个std::shared_ptr,这样它们会共享对同一资源的拥有权。

#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;
}

在上述示例中,我们将ptr1赋值给ptr2,它们现在都指向同一个整数资源,并且共享对该资源的拥有权。

引用计数和资源释放:

std::shared_ptr使用引用计数来跟踪资源的所有者数量。当最后一个std::shared_ptr析构或被赋予新的值时,引用计数会减少并检查是否需要释放资源。

#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;
}

在上述示例中,我们创建了三个std::shared_ptr对象ptr1、ptr2和ptr3,它们都指向同一个整数资源。当ptr1调用reset函数后,它不再拥有资源,引用计数减少并且资源被释放,但ptr2和ptr3仍然有效并拥有资源。

std::shared_ptr还提供了其他有用的成员函数,如get(返回底层指针)、use_count(返回引用计数)、unique(检查是否是唯一的拥有者)等。

使用std::shared_ptr可以有效避免内存泄漏和悬空指针等问题,并提供方便的资源管理机制。然而,要注意避免循环引用问题,因为它可能导致资源无法释放。

析构函数

std::shared_ptr的析构函数是由其模板参数指定的删除器(deleter)来执行的。可以通过提供自定义的删除器来自定义析构行为。

删除器是一个可调用对象,用于在std::shared_ptr的引用计数归零时执行资源的释放。删除器可以是函数指针、函数对象、Lambda表达式或自定义类型的对象。

以下是使用自定义删除器的示例:

#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;
}

在上述示例中,我们定义了一个名为CustomDeleter的结构体,其中重载了圆括号运算符,以实现自定义的删除行为。在main函数中,我们使用std::shared_ptr的构造函数来创建一个std::shared_ptr对象ptr,并将自定义删除器作为参数传递。

当std::shared_ptr的引用计数归零时,会调用自定义删除器的圆括号运算符来释放资源,并执行我们定义的自定义删除行为。在这个示例中,自定义删除器会输出一条消息,并删除指向整数的指针。

注意,当提供自定义删除器时,需要确保删除器与指针类型兼容,并遵循适当的资源释放规则。通过自定义删除器,可以实现更灵活的资源管理和析构行为,以满足特定的需求。

其他操作

在这里插入图片描述在这里插入图片描述

weak_ptr

C++11引入了std::weak_ptr作为一种智能指针类型,用于解决std::shared_ptr可能导致的循环引用问题。std::weak_ptr允许对由std::shared_ptr管理的对象进行弱引用,而不会增加引用计数,也不会阻止对象的销毁。

以下是std::weak_ptr的基本用法和示例:

创建std::weak_ptr对象

可以通过将std::shared_ptr对象转换为std::weak_ptr来创建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;
}

在上述示例中,我们创建了一个std::shared_ptr对象sharedPtr来管理一个整数资源,并通过将其转换为std::weak_ptr创建了一个std::weak_ptr对象weakPtr。注意,转换为std::weak_ptr不会增加资源的引用计数。

检查std::weak_ptr是否有效并访问资源

可以使用lock()函数来检查std::weak_ptr是否有效,并获取对资源的共享访问。

#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;
}

在上述示例中,我们在作用域中创建了一个std::shared_ptr对象sharedPtr,并将其转换为std::weak_ptr对象weakPtr。在作用域结束后,sharedPtr被销毁,资源被释放。在后续的代码中,我们通过调用lock()函数检查weakPtr是否有效,并访问资源。如果weakPtr有效,lock()函数将返回一个有效的std::shared_ptr对象,否则返回一个空指针。

查看资源

expired()

用于检查std::weak_ptr是否过期(即指向的资源是否已经被释放)。如果资源已经被释放,则返回true;否则返回false。

use_count()

用于获取与std::weak_ptr共享相同资源的有效std::shared_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;
}

在上述示例中,我们创建了一个std::shared_ptr对象sharedPtr来管理一个整数资源,并通过将其转换为std::weak_ptr对象weakPtr来创建一个弱引用。我们使用use_count()函数来获取与sharedPtr共享相同资源的有效std::shared_ptr对象的数量。在if语句中,我们使用expired()函数检查weakPtr是否过期,然后使用lock()函数获取有效的std::shared_ptr对象并输出其值。在后续的代码中,我们通过调用reset()函数将sharedPtr置空,释放资源,并检查weakPtr是否过期。

需要注意的是,由于std::weak_ptr不增加引用计数,所以调用use_count()函数返回的是与其共享资源的有效std::shared_ptr对象的数量。

其他操作

在这里插入图片描述

通过使用std::weak_ptr,我们可以避免std::shared_ptr可能导致的循环引用问题,并更灵活地管理资源的生命周期。

unique_ptr

std::unique_ptr是独占所有权的智能指针,意味着同一时间只能有一个std::unique_ptr拥有指针所指向的对象。当unique_ptr被销毁,其所指向的对象也会自动被销毁。

以下是std::unique_ptr的基本用法和示例:

创建std::unique_ptr对象

可以使用std::make_unique函数或直接使用std::unique_ptr的构造函数来创建std::unique_ptr对象。

#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;
}

在上述示例中,我们使用std::make_unique函数和构造函数分别创建了两个std::unique_ptr对象uniquePtr1和uniquePtr2来管理两个整数资源。注意,std::make_unique是C++14引入的函数,如果你使用的是C++11,可以直接使用std::unique_ptr的构造函数。

执行所有权的转移

std::unique_ptr具有独占所有权的特性,可以通过移动语义将所有权从一个std::unique_ptr转移到另一个std::unique_ptr。

#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;
}

在上述示例中,我们通过使用std::move将uniquePtr1的所有权转移到uniquePtr2。这样,uniquePtr2现在拥有原始资源,并且uniquePtr1不再拥有资源。

使用自定义删除器

可以通过提供自定义删除器来指定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;
}

在上述示例中,我们创建了一个带有自定义删除器CustomDeleter的std::unique_ptr对象uniquePtr。当uniquePtr被销毁时,自定义删除器将被调用,并负责释放资源。

std::unique_ptr提供了一种轻量级的智能指针,适用于管理单个所有权的对象,提供了高效的内存管理和安全的资源释放。

关于array

C++删除array需要使用delete[],由于C++无法区分pointer指向的是单个对象还是array,因此指针自动删除会出错,标准库对此提供了特殊版本。
在C++11中,引入了std::unique_ptr的数组版本,即std::unique_ptr<T[]>,用于管理动态分配的数组对象。

下面是std::unique_ptr管理动态数组的示例:

#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;
}

在上述示例中,我们使用std::unique_ptr<int[]>创建了一个std::unique_ptr对象uniquePtr来管理一个包含5个整数的动态数组。通过使用数组下标运算符[],我们可以访问和修改数组中的元素。当uniquePtr被销毁时,它会自动释放动态数组所占用的内存。

需要注意的是,std::unique_ptr<T[]>只适用于管理通过new T[]动态分配的数组,而不是指向已存在的数组,这个版本不提供操作符’*‘和’->'。在使用std::unique_ptr<T[]>时,不需要手动调用delete[]释放内存,std::unique_ptr会自动处理内存的释放。另外,这一版本不支持不同类型直接的转换,不允许指向派生元素类型。

其他操作

在这里插入图片描述

auto_ptr

std::auto_ptr是C++98标准引入的智能指针,用于管理动态分配的对象。然而,自从C++11起,std::auto_ptr已经被废弃,不推荐在新代码中使用,因为它存在一些缺陷和不安全的行为。
以下是一些关于std::auto_ptr的重要注意事项:

所有权的转移

与std::unique_ptr不同,std::auto_ptr支持所有权的转移,即可以将资源的所有权从一个std::auto_ptr转移到另一个std::auto_ptr。这意味着在转移所有权后,原始的std::auto_ptr将不再拥有资源。

不支持数组

std::auto_ptr只适用于管理单个对象,而不支持管理动态分配的数组。

删除器的限制

std::auto_ptr只支持使用默认的删除器,无法自定义删除器。这意味着在销毁std::auto_ptr时,只会调用delete来释放资源。

不安全的拷贝语义

std::auto_ptr的拷贝语义存在问题,它使用的是移动语义而不是传统的拷贝语义。这导致在拷贝后,原始的std::auto_ptr会失去对资源的所有权,可能导致资源的重复释放。

因此,建议使用C++11引入的更安全和更强大的智能指针类型,如std::unique_ptr用于独占所有权的情况,std::shared_ptr用于共享所有权的情况,以及std::weak_ptr用于解决循环引用问题。这些智能指针类型提供了更好的语义和更强的类型检查,能够更好地管理资源并提供更好的内存安全性。

补充

内存开销

相比于裸指针,智能指针通常会引入一定的内存开销。智能指针对象通常包含引用计数等额外的数据成员,这可能会占用更多的内存。虽然这个开销在大多数情况下可以忽略不计,但对于资源非常有限的嵌入式系统等特殊环境下,需要仔细考虑智能指针的使用。

不适合某些情况

智能指针并不是适用于所有情况的通用解决方案。在某些特定的应用场景下,例如与C接口进行交互、处理外部资源等,可能需要手动管理资源,而不适合使用智能指针。

语义差异和注意事项

不同类型的智能指针有不同的语义和行为,需要理解和注意其使用方式。例如,std::shared_ptr的共享所有权可能带来额外的开销和线程安全的问题,而std::unique_ptr则适用于独占所有权的场景。此外,智能指针的使用还需要注意循环引用、空指针检查、潜在的性能影响等问题。

虽然智能指针有其缺陷和注意事项,但它们在大多数情况下提供了方便、安全和可靠的资源管理方式。使用智能指针时,需要理解其语义和行为,并结合具体的应用场景进行选择和使用。

猜你喜欢

转载自blog.csdn.net/qq_44616044/article/details/131184594