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

news/2024/7/24 1:03:53

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也可直接输出myTuplereturn 0;
}

操作函数

在这里插入图片描述

std::tuple_size

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

输出结果为: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; // 输出:42std::cout << *ptr2 << std::endl; // 输出:100return 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; // 输出:42std::cout << *ptr2 << std::endl; // 输出:42return 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; // 输出:42std::cout << *ptr2 << std::endl; // 输出:42std::cout << *ptr3 << std::endl; // 输出:42ptr1.reset();std::cout << std::boolalpha;std::cout << "ptr1 is nullptr: " << (ptr1 == nullptr) << std::endl; // 输出:truestd::cout << "ptr2 is nullptr: " << (ptr2 == nullptr) << std::endl; // 输出:falsestd::cout << "ptr3 is nullptr: " << (ptr3 == nullptr) << std::endl; // 输出:falsereturn 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;// 输出:42if (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;// 输出:42if (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;// 输出:1std::cout << "use_count: " << sharedPtr.use_count() << std::endl;if (weakPtr.expired()) {std::cout << "Resource has been released" << std::endl;} else {// 输出:42std::cout << "Value: " << *weakPtr.lock() << std::endl;}sharedPtr.reset();// 输出:0std::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));// 输出:42std::cout << *uniquePtr1 << std::endl;// 输出:100std::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);// 输出:42std::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));// 输出:42std::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 5std::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则适用于独占所有权的场景。此外,智能指针的使用还需要注意循环引用、空指针检查、潜在的性能影响等问题。

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


http://wed.xjx100/news/301227.html

相关文章

基于DDD实现的用户注册流程,很优雅!

欢迎回来&#xff0c;我是飘渺。今天继续更新DDD&微服务的系列文章。 在前面的文章中&#xff0c;我们深入探讨了DDD的核心概念。我理解&#xff0c;对于初次接触这些概念的你来说&#xff0c;可能难以一次性完全记住。但别担心&#xff0c;学习DDD并不仅仅是理论的理解&am…

工商业储能解读

工商业储能解读 0、前言1、2022-2023年工商业储能相关利好政策1.1 2022年1月4日1.2 2022年1月18日1.3 2022年2月10日1.4 2022年3月21日1.5 2022年3月22日1.6 2022年3月29日1.7 2022年4月2日1.8 2022年4月13日1.9 2022年4月25日1.10 2022年5月25日1.11 2022年5月30日1.12 2022年…

如何使用ArcGIS制作SketchUp格式三维建筑

GIS数据也可以和传统的三维建模软件进行结合&#xff0c;在很长一段时间内&#xff0c;一直有客户问如何将水经微图中下载的建筑数据转换为SketchUp模型&#xff0c;这里给大家找到了一种解决方案&#xff0c;可以通过插件进行转换&#xff0c;希望能够对你有所帮助。 加载插件…

网络编程与自动化(python)

20.1 网络编程与自动化概述 传统网络运维困境大家在日常的网络运维中是否遇到过如下问题: 设备升级:现网有数千台网络设备,你需要周期性、批量性地对设备进行升级。配置审计:企业年度需要对设备进行配置审计。例如要求所有设备开启sTelnet功能,以太网交换机配置生成树安全…

linux初级阶段性面试题整理(一)

文章目录 网络基础1.OSI七层模型是什么&#xff1f;2.TCP/IP五层模型是什么&#xff1f;3.IPv4的ABC类地址范围4.三种私有网络地址的范围5.常用的TCP端口号及功能6.常用的UDP端口号及其功能7.TCP、UDP协议属于七层模型的哪层&#xff1f;并写出TCP报文段中的三个控制位8.VLAN I…

商品编号篡改测试-业务安全测试实操(7)

商品编号篡改测试,邮箱和用户篡改测试 手机号码篡改测试-业务安全测试实操(6)_luozhonghua2000的博客-CSDN博客 邮箱和用户篡改测试 测试原理和方法 在发送邮件或站内消息时,篡改其中的发件人参数,导致攻击者可以伪造发信人进行钓鱼攻击等操作,这也是一种平行权限绕过漏洞…

Ansys Zemax | 如何在OpticStudio中建模和设计真实波片

本文介绍了如何在 OpticStudio 中建模和设计真实的单色和消色差波片。它将演示如何使用双折射材料&#xff0c;通过构建评价函数来计算相位延迟&#xff0c;并使用 Universal Plot 将相位延迟与波片厚度的关系可视化。&#xff08;联系我们获取文章附件&#xff09; 双折射材料…

git使用方法小结

1.如何git push直接推到远程分支&#xff1f; 先建立本地分支和远程分支的关联 git branch --set-upstream-toorigin/remote_branch your_branch 然后git push 否则就需要git push origin your_branch 2.查看本地版本和远程版本的对应关系 git branch -vv 3.查看本地和远程…