c++11的新特性:

目录

{}初始化 initializer_list

关键字

auto

decltype

decltype(auto)

nullptr

noexcept

explicit

default delete

using

constexpr constexpr_if

override

final

alignas

static_assert

thread_loca

范围for

STL的扩展

新容器

array:静态数组

forward_list

unordered_map unordered_set 两个容器

新接口

右值引用与左值引用

move函数

将亡值

万能引用

完美转发forward()

lambda

语法

底层实现

可变模板参数

可变参数

可变模板参数(variadic template parameters)

包装器 fuction bind

智能指针

Boost库

实现简单智能指针

引用计数

模拟实现unique_ptr

模拟实现shared_ptr

weak_ptr

问题解决

介绍

weak_ptr模拟实现

智能指针的定制删除器

定制删除器下的shared_ptr


{}初始化 initializer_list <class T>

一切皆可用{}初始化 并且可以不写=

p0(0,0) == p1 ={0,0} == p1{0,0} Point* ptr = new Point[2]{ {0,0},{1,1} }

.initializer_list <class T>

常量区list 类里边有两个指针start 与 last vector其中一个构造函数有一个是用initializer list初始化 initializer_list只能用于初始化容器,不能用于其他类型的对象初始化。此外,initializer_list本身并不提供任何操作,它仅仅是一种容器初始化方法

关键字

auto

自动推导变量类型,当使用auto关键字声明变量时,编译器会自动根据给定的表达式或表达式列表推断变量的类型。

decltype

用于获取表达式的返回类型或变量类型。它可以根据给定的表达式或函数调用结果,推断出表达式的返回类型或变量的类型。 auto关键字主要用于声明变量类型,而decltype则主要用于获取表达式的返回类型或变量类型

#include <iostream>
#include <vector>
​
int main() {
    int i = 5;
    decltype(i) j = i;  // 推断出 j 的类型为 int
    std::cout << j << std::endl;  // 输出: 5
​
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    decltype(numbers) copy = numbers;  // 推断出 copy 的类型为 std::vector<int>
    for (auto num : copy) {
        std::cout << num << " ";  // 输出: 1 2 3 4 5
    }
    
    auto func = []() -> int { return 42; };
    decltype(func()) result = func();  // 推断出 result 的类型为 int
    std::cout << result << std::endl;  // 输出: 42
    
    return 0;
​
}

在这些示例中,我们使用 decltype 来获取变量的静态类型,而无需显式指定类型。decltype(i) 推断出变量 j 的类型为 intdecltype(numbers) 推断出变量 copy 的类型为 std::vectordecltype(func()) 推断出变量 result 的类型为 int。这样在声明变量时,我们可以使用 decltype 简化类型的定义。

需要注意的是,decltype 并不会执行表达式,只是用于推断表达式的类型。因此,在使用 decltype 时需要保证表达式是编译时可求值的。

decltype(auto)

用于推断返回类型。它允许编译器自动推断表达式的类型,并在编译时生成类型别名,而不是在运行时进行类型检查。 使用 decltype(auto) 可以提高代码的可读性和效率,因为它可以自动处理类型推断,从而避免在每次返回表达式时都需要显式地指定类型。这对于在函数或循环中返回一系列可能具有不同类型的结果时特别有用。

#include <iostream>
#include <vector>
​
std::vector<int> get_numbers() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    return numbers;
}
​
int main() {
    auto result = decltype(auto) { get_numbers() };
    for (auto num : result) {
        std::cout << num << " ";
    }
    return 0;
}

在这个例子中,decltype(auto) 自动推断出 get_numbers() 函数的返回类型为 std::vector,并将其赋值给 result。然后,我们可以在循环中安全地使用 result,因为它会自动更新为 get_numbers() 的返回值类型。

需要注意的是,decltype(auto) 仅在表达式返回一个变量时才有效。如果返回一个常量或函数调用,编译器将无法推断类型。此外,decltype(auto) 在嵌套的函数或类中可能无法正常工作,因为它依赖于表达式的上下文来确定类型。

nullptr

表示空指针

noexcept

它承诺在正常情况下不会抛出任何异常,这意味着函数的所有异常处理语句都必须是 noexcept 函数。

explicit

用于声明变量、函数或类成员的访问权限。当使用explicit关键字声明一个变量、 函数或类成员时,编译器会明确地指定其访问权限,从而避免某些默认的访问权限导致的潜在错误。

<1. 声明静态成员函数:使用explicit关键字声明的静态成员函数只能被下列函数调用: - 该类的成员函数。 - 该类的友元函数。 - 直接或间接地通过成员函数调用。

<2. 声明成员函数:使用explicit关键字声明的成员函数只能被下列函数调用:

  • 该类的成员函数。 该类的友元函数。

<3. 声明变量:使用explicit关键字声明的变量只能在类的成员函数或友元函数中访问。 需要注意,explicit关键字不能用于构造函数和析构函数。

default delete

default:用于指定默认生成或默认删除函数。

delete:用于删除函数或禁用特定的函数。

using

using 是 C++ 中的一个关键字,用于声明命名空间或定义别名。using 关键字可以用于以下两种情况:

  1. 命名空间别名:using 可以用于为命名空间指定一个别名。这样,在当前作用域中,可以使用别名来访问命名空间中的变量或函数。

namespace  ns  {
     int  x  =  10;
     void  foo()  {
         std::cout  <<  "Hello,  namespace  ns!"  <<  std::endl;
     }
}
​
int  main()  {
     using  ns::x;   //  为命名空间  ns  指定一个别名  x
     using  ns::foo;   //  为命名空间  ns  指定一个别名  foo
​
     x  =  15;   //  使用别名  x  访问命名空间  ns  中的变量  x
     foo();   //  使用别名  foo  调用命名空间  ns  中的函数  foo
​
     return  0;
}

在这个示例中,我们为命名空间 ns 指定了一个别名 x,并使用该别名访问该命名空间中的变量。同样,我们为命名空间 ns 指定了一个别名 foo,并使用该别名调用该命名空间中的函数。

  1. 类型别名:using 可以用于为类型指定一个别名。这样,在当前作用域中,可以使用别名来表示类型。

template<typename  T>
void  print(T  value)  {
     std::cout  <<  "Value:  "  <<  value  <<  std::endl;
}
​
int  main()  {
     using  std::print;   //  为标准库中的  print  函数指定一个别名
​
     print(42);   //  使用别名  print  调用标准库中的  print  函数,参数为  int  类型
     print(3.14);   //  使用别名  print  调用标准库中的  print  函数,参数为  double  类型
​
     return  0;
}

在这个示例中,我们为标准库中的 print 函数指定了一个别名 print。然后,我们使用该别名调用 print 函数,传入不同类型的参数。

需要注�,using 关键字不能用于函数或类的成员变量,只能用于命名空间和类型别名。此外,在模板中使用 using 时,需要确保别名与模板参数类型匹配。

在模板编程中,有时需要访问模板类或函数的非类型参数。 传统的using声明无法直接访问这些参数,因为它们是通过类型推导而不是名称解析来确定的。

在C++11中,引入了using声明的扩展,使得可以更方便地访问模板类或函数的非类型参数。 可以使用using声明来引用模板类或函数的非类型参数的类型。这种扩展是通过使用特定的语法来实现的,例如使用类型前缀或类型占位符例子:

template <typename T, T Value>
class MyTemplateClass {
public:
    void printValue() {
        std::cout << "Value: " << Value << std::endl;
    }
};
​
template <typename T>
void myTemplateFunction(T value) {
    // 使用 using 声明来访问非类型参数的类型
    using ValueType = typename std::remove_const<T>::type;
    ValueType valueToPrint = value;
    std::cout << "Value: " << valueToPrint << std::endl;
}

constexpr constexpr_if

constexprconstexpr_if 是 C++11 中引入的两个新的关键字,用于在编译时计算表达式的值。

1.constexp

constexpr 是一个编译时关键字,用于声明在编译时计算表达式值的变量或函数。如果一个函数或变量被声明为 constexpr,则它只能在函数定义或变量声明时被初始化,而不能在运行时被赋值。

例如:

constexpr  int  i  =  5;   //  在编译时计算  i  的值
constexpr  int  j  =  i  +  1;   //  在编译时计算  j  的值

在上述示例中,变量 ij 都被声明为 constexpr,因此它们的值在编译时就被计算出来。在运行时,这些变量的值不会改变。

2.constexpr_if

constexpr_if 是 C++17 中引入的一个新的关键字,它允许在编译时根据条件计算表达式的值。constexpr_if 的语法类似于 if 语句,但是它只在编译时计算表达式的值,而不是在运行时。

例如:

constexpr  int  i  =  5;
constexpr  int  j  =  constexpr_if<(i  >  0)>(i  *  i,  i  -  1);   //  在编译时计算  j  的值

在上述示例中,constexpr_if<(i > 0)> 是一个编译时条件表达式,它检查变量 i 是否大于 0。如果 i 大于 0,则 constexpr_if 表达式的值为 i * i,否则它的值为 i - 1

需要注意,constexprconstexpr_if 只能在函数定义或变量声明时使用,而不能在运行时使用。此外,constexprconstexpr_if 只能在编译时计算表达式的值,因此它们只能用于在编译时已知表达式值的变量或函数。

override

在C++中,override关键字用于显式地指示子类中的某个成员函数(指非虚函数)是对父类中虚函数的覆盖。如果子类中的函数与父类中的虚函数具有相同的名称、参数列表和返回类型,但子类中的函数声明为override,则该函数将被视为对父类中虚函数的覆盖。

使用override关键字可以提高代码的可读性和可维护性,因为它可以清晰地表明子类中的函数是对父类中虚函数的覆盖。同时,使用override关键字还可以在编译时检查函数的覆盖情况,避免在运行时出现虚函数调用异常。

以下是一个使用override关键字的示例:

#include  <iostream>
​
class  A  {
public:
     virtual  void  foo();
};
​
class  B  :  public  A  {
public:
     //  子类中的foo函数是对父类中foo函数的覆盖
     override  void  foo();
};
​
A::foo()  {
     std::cout  <<  "A::foo  called"  <<  std::endl;
}
​
B::foo()  {
     std::cout  <<  "B::foo  called"  <<  std::endl;
}
​
int  main()  {
     B  b;
     b.foo();   //  输出  "B::foo  called"
     return  0;
}

在上述示例中,B类继承自A类,并覆盖了A类中的虚函数foo。在B类中,foo函数被声明为override,以表明它是对A类中虚函数foo的覆盖。当在B对象上调用foo函数时,会执行B类中的foo函数,而不是A类中的foo函数。

final

在C++中,final关键字可以用于声明一个类或类的成员函数不能被继承或覆盖。使用final关键字可以提高代码的可读性和可维护性,因为它可以清晰地表明一个类或类的成员函数不能被继承或覆盖。

以下是一个使用final关键字的示例:

#include  <iostream>
​
class  A  {
public:
     virtual  void  foo()  {
         std::cout  <<  "A::foo  called"  <<  std::endl;
     }
};
​
class  B  :  public  A  {
public:
     //  子类中的foo函数试图覆盖父类中的foo函数,但由于A::foo被声明为final,所以子类中的foo函数无法继承
     void  foo()  override  final  {
         std::cout  <<  "B::foo  called"  <<  std::endl;
     }
};
​
int  main()  {
     B  b;
     b.foo();  //  输出  "B::foo  called"
     return  0;
}

在上述示例中,A类中的虚函数foo被声明为final,以表明该函数不能被子类覆盖。因此,在B类中,尽管试图覆盖foo函数,但由于A::foo被声明为final,所以子类中的foo函数无法继承。当在B对象上调用foo函数时,会执行子类中的foo函数,而不是父类中的foo函数。

需要注意的是,final关键字只能用于类或类的成员函数,而不能用于其他类型的对象。此外,final关键字只能用于虚函数,而不能用于非虚函数。

alignas

alignas 是 C++11 引入的一个关键字,用于指定变量或类型应如何对齐。对齐可以提高内存访问的效率,特别是对于某些处理器架构,不同的对齐策略可能会导致不同的性能表现。

alignas 关键字可以用于声明变量或类型,以指定其对齐需求。例如,以下代码将创建一个 alignas(16) int 类型变量,这意味着该变量将按照 16 字节的边界对齐:

alignas(16) int x;

此外,alignas 还可以与其他关键字一起使用,如 structclass 等,以指定整个结构体或类的对齐需求。

需要注意的是,alignas 的值必须是 2 的幂,并且不能超过可用的最大对齐限制。此外,不同的编译器和平台可能对齐要求有所不同,因此在使用 alignas 时需要考虑到目标平台的特性。

static_assert

static_assert 是 C++11 引入的一个关键字,用于在编译时进行静态断言。静态断言是一种用于检查表达式是否为真的机制,如果表达式为假,则编译时会抛出错误。

static_assert 的语法如下:

static_assert(expression,  message);

其中,expression 是要检查的表达式,message 是可选的错误消息,用于描述断言失败的情况。如果 expression 为真,则编译通过;如果 expression 为假,则编译时会抛出错误,并在错误信息中包含 message

例如,以下代码使用 static_assert 来检查 sizeof(int) 是否为 4:

static_assert(sizeof(int)  ==  4,  "int  的长度不是  4");

如果 sizeof(int) 不是 4,则编译时会抛出错误,并显示错误信息 "int 的长度不是 4"。

使用 static_assert 可以提高代码的可读性和可维护性,因为它可以在编译时检查表达式的值,避免在运行时出现错误。此外,使用 static_assert 还可以帮助开发者发现潜在的错误,避免在运行时出现异常。

thread_loca

thread_local 是 C++11 引入的一个关键字,用于定义线程局部存储变量。线程局部存储意味着每个线程都有其自己独立的变量实例,各个线程互不干扰。

在 C++ 中,thread_local 可以用于定义变量,使其在不同线程中具有各自独立的副本。每个线程都有自己的变量实例,对其进行的修改不会影响其他线程中的变量。

以下是一个简单的示例,展示如何使用 thread_local 关键字:

#include <iostream>
#include <thread>
​
thread_local int count = 0;
​
void incrementCount() {
    count++;
    std::cout << "Thread " << std::this_thread::get_id() << ": count = " << count << std::endl;
}
​
int main() {
    std::thread t1(incrementCount);
    std::thread t2(incrementCount);
​
    t1.join();
    t2.join();
​
    return 0;
}

在这个示例中,我们定义了一个 thread_local 变量 count,初始值为 0。然后,我们创建了两个线程 t1t2,它们分别调用函数 incrementCount。在每个线程中,count 都会增加,并打印出线程的 ID 和当前 count 的值。由于每个线程都有自己的 count 变量,因此线程之间的修改不会相互影响。

使用 thread_local 可以很方便地管理线程局部数据,适用于在多线程应用程序中需要跟踪每个线程的独立状态或变量的情况。需要注意的是,thread_local 变量只能在全局或静态作用域中定义,而不能在局部作用域中定义。

范围for

for (auto element : container) { // 循环体代码 }

STL的扩展

新容器
array:静态数组

<1. 数组初始化:C++11允许在声明数组时进行初始化,这使得数组声明和初始化更加简洁。可以使用花括号{}或初始化列表语法进行初始化 <2. 数组大小:C++11中的数组大小在编译时确定,不能动态改变。但是,可以通过std::array模板类创建动态数组。 <3. 数组下标:C++11中的数组下标从0开始。

可以通过arr[i]访问数组元素,其中i是0到arr.size() - 1之间的整数。 <4. 数组切片:C++11提供了std::vectorstd::array切片操作,可以方便地获取数组的一部分。 std::vector<int> vec({1, 2, 3, 4, 5}); std::vector<int> slice(vec.begin(), vec.begin() + 2); // 获取前两个元素 <5.越界检查 []访问是一个指针的接引用

forward_list

C++11引入的一种单向链表容器,它与"list"容器相比,具有更低的内存开销和更高的插入/删除性能。 正如其名称所示,"forward_list"只能按照一个方向进行遍历,从头到尾,无法逆向访问。 需要注意的是,"forward_list"不支持随机访问,无法通过下标访问元素,也无法使用push_back()函数在尾部插入元素。 如果需要在尾部插入元素或进行随机访问,可以考虑使用"std::list"容器。

unordered_map unordered_set 两个容器

新接口

Iteartor中:cbegin cend crbegin crend

所有的容器都支持{}列表初始化构造函数 曹勇initializer_list来初始化 所有容器新增了emplace emplace_back emplace_front 移动构造 移动赋值

右值引用与左值引用

左值引用的使用场景:1.做参数 2.做返回值 价值:减少拷贝

右值引用给左值起别名:可以引用move操作后的左值 move左值的作用主要是用于将一个右值对象的所有权转移给另一个左值对象,同时保留右值对象的原有值。

move函数

只能用于左值引用,而且左值引用在转移对象所有权后,原本的左值引用将变为右值引用。因此,在使用move函数时,需要确保左值引用具有可修改性。否则,可能会导致编译错误或未定义行为。 使用场景:

  1. 容器操作:在处理容器(如vector、list等)时,右值引用可以用于高效地移动容器中的元素。通过使用右值引用,我们可以避免不必要的复制操作,从而提高代码的效率。

  2. 函数参数传递:在函数中,右值引用可以用于传递临时对象或表达式。通过这种方式,函数可以接收可修改的对象,而不是拷贝对象。这可以提高函数的效率,并且减少了对象的拷贝操作。

  3. 移动语义:右值引用和移动语义是C++11引入的新特性,用于实现对象的快速移动。通过使用右值引用和移动构造函数或移动赋值操作,我们可以避免不必要的复制操作,从而提高代码的性能。

  4. 智能指针和资源管理:右值引用还可以用于智能指针和资源管理,例如std::unique_ptr和std::shared_ptr。通过使用右值引用,我们可以更方便地管理资源,并在适当的时候释放它们。 当函数参数是caonst int & x 与 int&& x时 在右值调用函数时候编译器会选择更匹配的有右值引用的版本

move 后的数据:

  1. 状态被置为无效或清理状态:被移动的目标对象通常会从有效的状态变为无效状态,比如一个指向已经被释放的内存的指针会变成空指针或者释放原有内存,以便移动过来的资源使用。

  2. 资源被释放:如果被移动的目标对象是动态分配的资源,如堆内存、文件句柄、网络连接等,移动后会将其释放,避免资源的重复使用和内存泄漏。

  3. 数据拷贝:对于可复制的数据类型,如字符串、数组等,移动后会被拷贝一份新的数据到目标对象中,原有对象中的数据会被置为无效状态。

移动拷贝 和将亡值与自己交换数据 自己拿走被人的 不要的直接给这个将要析构的人

将亡值

(rvalue)是指处于即将销毁状态的对象,将亡值可以看作是一个即将被移动(move)或者被转移(transfer)所有权的临时对象。 int&& x = 10; // 将亡值:10 是一个临时对象

int x = 10; ///move 函数:使用 std::move 函数可以将一个对象转换为将亡值。 int&& y = std::move(x); // 将亡值:x 被转移为 y

int getValue() { return 42; // 将亡值:返回一个临时对象

万能引用

模板参数中的移动语义是万能引用 既能接受左值又能接受右值 (在实参是左值时引用折叠) 注意在类里边万能引用是不能用的因为 一个成员只有一个函数 因为有关联所以不能实例化新的函数 有一个思路就是把类中的函数写成函数模板 ​ 注意 :::::给左值进行移动语义 尽管是右值,但它实际上关联的是左值 在实际应用中,要确保右值引用绑定到真正的右值,以避免不必要的资源拷贝和优化资源管理

完美转发forward<T>()

forward<T>() t是左值引用保持左值属性 t是右值引用保持右值属性 可以解决上边的这个可能出现的矛盾问题 使用场景:在传参数或者在函数中使用引用时都要用这个完美转发 只要你希望在某一行要保持原本的属性就得用这个

lambda
语法
std::sort(numbers.begin(), numbers.end(), [](int a, int b)->bool {
    return a > b;
});

[捕获列表] (参数列表) -> 返回类型 { 函数体 }

1其中,捕获列表指定了lambda函数可以访问的外部变量。捕获列表可以为空, 也可以包括零个或多个外部变量,用逗号分隔。捕获列表可以按值捕获外部变量 (变量在lambda函数创建时拷贝),也可以按引用捕获外部变量。

  • "[=]" :以值捕获方式,表示将所有外部变量以值的方式进行捕获。在lambda函数体内,可以访问外部作用域中的变量的副本。

  • "[&]" :以引用捕获方式,表示将所有外部变量以引用的方式进行捕获。在lambda函数体内,可以直接访问并修改外部作用域中的变量。

  • "[x]" :以值捕获方式,只捕获变量x。

  • "[&x]" :以引用捕获方式,只捕获变量x。

  • "[=, &x, &y]" :以值捕获方式捕获除变量x和y之外的所有变量,以引用捕获方式捕获变量x和y。

2参数列表指定了lambda函数的输入参数。参数列表可以为空,也可以包括零个或多个参数,用逗号分隔。 3在lambda表达式中,返回类型可以省略,编译器会根据函数体自动推导返回类型。 当函数体只包含一个return语句,或者没有return语句时,编译器可以自动确定返回类型。 如果函数体中包含多个语句,或者使用了非局部变量,那么返回类型可能无法自动推导。 在这种情况下,需要显式指定返回类型。

底层实现

Lambda表达式在C++中的底层实现依赖于编译器和运行时环境。编译器需要将lambda表达式转换为可执行代码,这通常涉及到以下几个步骤:

  1. 声明:编译器需要知道lambda表达式的类型和参数列表。这通常通过使用模板和元编程技术来实现。

  2. 捕获:编译器需要捕获lambda表达式中使用的所有变量,并将其存储在一个名为“闭包”的数据结构中。这些变量可以是外部作用域中的变量,也可以是局部变量。

  3. 执行:编译器将lambda表达式转换为可执行代码,并执行闭包中的代码。这通常涉及到使用一种称为“内联函数”的技术,将闭包中的代码嵌入到生成的代码中。

在底层实现中,编译器通常使用一种称为“闭包”的数据结构来存储捕获的变量。闭包是一种能够访问其外部作用域中变量的数据结构,它在执行期间动态创建。编译器还需要使用一种算法来计算lambda表达式的语法树并生成相应的可执行代码。 Lambda表达式在底层机制上类似于C++中的仿函数(Function Object)。Lambda表达式和仿函数都可以用作可调用对象,它们都可以作为函数参数传递,用于实现回调机制,或者作为算法的谓词。

在C++中,Lambda表达式被设计为一种简洁的语法形式,更直观和易于使用。而仿函数则是通过定义一个类或结构体,重载函数调用运算符(operator())来实现的。虽然表达形式不同,但它们在功能上是相似的。

当使用Lambda表达式时,编译器会为其生成一个临时的闭包对象,而这个闭包对象实际上就是一个类似于仿函数的对象。这个闭包对象包含了Lambda表达式中捕获的变量,并且可以通过重载函数调用运算符(operator())来执行Lambda表达式的操作。

Lambda表达式和仿函数都可以实现具有状态的可调用对象,并且可以在需要函数对象的任何地方使用。Lambda表达式的好处在于它们更简洁、更灵活,可以直接在代码中进行定义,而无需显式定义一个类或结构体。

可变模板参数
可变参数

可变参数(C语言)以printf为例 在C语言中,printf函数中的可变参数是指函数可以接受任意数量和类型的参数。这些可变参数由省略号(...)表示,并且在函数内部通过宏va_list、va_start、va_arg和va_end进行操作和访问。

以下是可变参数的典型用法:

  1. 使用va_list声明一个变量,用于存储可变参数的信息。

  2. 使用va_start宏初始化va_list变量,将其与最后一个固定参数之后的第一个可变参数关联起来。

  3. 使用va_arg宏获取可变参数的值,根据需要指定参数的类型。

  4. 使用va_end宏结束对可变参数的访问,释放相关资源。

#include <stdio.h>
#include <stdarg.h>
​
void myprintf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
​
    while (*format != '\0') {
        if (*format == '%') {
            format++;
            switch (*format) {
                case 'd': {
                    int value = va_arg(args, int);
                    printf("%d ", value);
                    break;
                }
                case 'f': {
                    double value = va_arg(args, double);
                    printf("%f ", value);
                    break;
                }
                case 's': {
                    char* value = va_arg(args, char*);
                    printf("%s ", value);
                    break;
                }
                default:
                    break;
            }
        } else {
            putchar(*format);
        }
        format++;
    }
    
    va_end(args);
​
}
​
int main() {
    int a = 10;
    double b = 20.5;
    char str[] = "Hello, world!";
​
    myprintf("a = %d, b = %f, str = %s\n", a, b, str);
    
    return 0;
​
}

要注意的是,使用可变参数时要小心处理类型匹配和指针的问题,确保可变参数的类型与格式字符串中的占位符匹配。另外,可变参数的使用需要依赖于C标准库中stdarg.h头文件中定义的宏和函数。

可变模板参数(variadic template parameters)

在模板参数表中,可以使用 class...typename... 的形式来声明可变模板参数。这样的语法使得模板可以接受零个或多个参数,并将这些参数作为参数包(parameter pack)来处理。

可变模板参数可以用于多种情况,例如:

  1. 在模板函数或类中以可变形式接受任意数量的参数。

  2. 递归处理参数包中的每个参数。

  3. 在模板特化或模板元编程中使用参数包来进行元算操作。

#include <iostream>
// 使用可变模板参数递归打印参数包中的每个值
void printValues()/结束函数        必须要有
{
    std::cout << "End of parameter pack" << std::endl;
}
​
template<typename T, typename... Args>
void printValues(T value, Args... args)
{
    std::cout << value << " ";
    std::cout << sizeof...(args) << endl;  //   sizeof 用时...在外部 很特别
    printValues(args...);
}
​
int main()
{
    printValues(1, 2, 3, 4, 5);
    return 0;
}

在这个示例中,printValues 函数使用可变模板参数来实现递归打印参数包中的值。在每个递归调用中,函数接受一个参数并打印它,然后通过递归调用自身来处理剩余的参数。

当调用 printValues(1, 2, 3, 4, 5) 时,它将依次打印 1、2、3、4、5,然后在最后一个递归调用中输出 "End of parameter pack"。

可变参数必须要递归调用

更牛逼的代码:

void printValues()/结束函数        必须要有
{
    //std::cout << std::endl;
}
​
template<typename T, typename... Args>
void printValues(T value, Args... args)
{
    std::cout << value << " ";
    printValues(args...);
}template<class...Args>
void CppPrint(Args... args)
{
    int a[] = { (printValues(args),0)... };
//这是一个使用折叠表达式(fold expression)的语法,它允许将多个表达式展开为编译期常量的初始化列表。
//在这行代码中,`(printValues(args), 0)` 是一个逗号表达式,它会先执行 `printValues(args)` 表达式,然后返回0。使用折叠表达式 `(printValues(args), 0)...` 可以将多个 `(printValues(args), 0)` 的结果展开为编译期常量的初始化列表。
//然而,对于 `int a[]` 这样的数组声明,数组的大小应该在编译时确定。对于包含运行时大小的数组,你可以使用 `std::array` 或动态内存分配来解决。
//    std::array<int, sizeof...(Args)> arr = { (printValues(args), 0)... };
//    std::array<int, sizeof...(Args)> arr = { (printValues(args...), 0)};
//    int a[] = { (printValues(args...),0) };
/三种方法都是可以的
    std::cout << std::endl;
}
int  main()
{
    CppPrint(1, 2.5, "hello", 3);
    return  0;
}

有一个小技巧:在构造对象时候 可以用可变模板参数 例子:

 template<class...Args>
Data* Create(Args...args)
{
Data* ret = new Data(2019,19,15);
return ret;
}

这样使用Create函数就可以多参数创建一个对象 注意在构造函数所有成员都要设置默认参数

这样 emplace 模板函数 我们就能理解了

template<class... Args>
iterator emplace(const_iterator position,Args&&...args);//(可变模板参数 + 万能引用)
mylist.emplace(make_pair(10,'a'));
mylist.emplace(10,'a');
mylist.emplace({10,'a'});

注意和push_front的区别: push_front是先构造pair中对象再移动构造(也有可能是拷贝构造因为是万能引用) emplace是直接构造对象 直接作为参数包进入函数体

包装器 fuction bind

在C++中,std::function是C++标准库<functional>中的一个模板类,用于封装可调用对象(如函数、函数指针、Lambda表达式、仿函数等)。

std::function可以存储和管理不同类型的可调用对象,并可以通过调用operator()来执行这些对象,就像调用普通函数一样。它提供了一种通用性的方式来处理可调用对象,无论其具体类型如何。

使用std::function有以下几个主要用途:

  1. 函数回调和事件处理:std::function可以用作回调函数的容器,用于在某个事件触发时执行相应的操作。

  2. 泛型设计:std::function可以作为参数或返回类型,使得函数可以接受各种不同类型的可调用对象。

  3. 函数对象包装:将函数对象或Lambda表达式封装为std::function对象,使其可以以统一的方式进行管理和调用。

// 函数添加器
int add(int a, int b) {
    return a + b;
}
​
int main() {
    std::function<int(int, int)> func;  // 声明一个接受两个int参数并返回int的std::function对象
​
    func = add;  // 将add函数赋值给func
    std::cout << func(1, 2) << std::endl;  // 输出结果:3
    
    // 使用Lambda表达式
    func = [](int a, int b) { return a * b; };
    std::cout << func(2, 3) << std::endl;  // 输出结果:6
    
    return 0;
​
}

在这个示例中,我们声明了一个类型为std::function<int(int, int)>的对象func,它可以接受两个int参数并返回int。我们将不同类型的可调用对象(add函数和Lambda表达式)分别赋值给func,并通过调用func来执行相应的操作。

std::function提供了一种灵活且类型安全的方式来处理可调用对象,使得函数对象的管理和调用更加方便和统一 声明: template<class Ret, class... Args> class function<Ret(Args...)>;

例子:逆波兰表达式的计算
​
#include <iostream>
#include <map>
#include <functional>
#include <stack>
#include <vector>
using namespace std;
​
map<string, function<int(int, int)>> cmdFuncMap = {
    {"+", [](int x, int y) { return x + y; }},
    {"-", [](int x, int y) { return x - y; }},
    {"*", [](int x, int y) { return x * y; }},
    {"/", [](int x, int y) { return x / y; }}
};
​
int evaluateExpression(vector<string> tokens) {
    stack<int> st;
    for (auto& str : tokens) {
        if (cmdFuncMap.count(str)) {
            // Operator
            int right = st.top();
            st.pop();
            int left = st.top();
            st.pop();
            st.push(cmdFuncMap[str](left, right));
        } else {
            // Operand
            st.push(stoi(str));
        }
    }
    return st.top();
}
​
int main() {
    vector<string> tokens = {"2", "3", "*", "4", "+"};
    int result = evaluateExpression(tokens);
    cout << "Result: " << result << endl;
    return 0;
}

bind

bind函数的作用是将一个函数绑定到一个特定的上下文或者对象上,使得该函数在调用时的上下文是绑定的上下文, 而不是当前的上下文。这样做的好处是可以将函数封装起来,隐藏其内部实现,同时也可以实现不同上下文之间的功能传递。

在不同的编程语言和场景中,bind函数的具体实现和用法可能有所不同。以下是一些常见的bind函数应用场景:

  1. 在JavaScript中,bind函数可以用于改变函数的执行对象。通过将一个新的执行对象和需要绑定的函数参数传入bind函数,可以实现函数在特定上下文下的调用。

  2. 在C++中,bind函数是一个通用的函数适配器,它接受一个可调用对象(如函数对象、函数指针、成员函数指针等)和一组参数, 生成一个新的可调用对象。这样可以把原可调用对象的某些参数预先绑定到给定的变量中,从而实现特定上下文下的调用。

  3. 在网络编程中,bind函数可以用于将回调函数与特定的地址和端口号绑定,以便在底层的网络框架有数据过来时,通过回调函数通知业务层进行处理。

1
#include <iostream>
#include <functional>
​
void greet(const std::string& name) {
    std::cout << "Hello, " << name << "!" << std::endl;
}
​
int main() {
    // 使用bind函数将greet函数和参数部分绑定
    auto greetFunc = std::bind(greet, "Alice");
​
    // 调用绑定后的函数
    greetFunc();  // 输出:Hello, Alice!
    
    return 0;
​
}
///2
int sum(int a, int b) {
return a + b;
}
int main() {
// 使用bind函数将sum函数和参数部分绑定
auto addFunc = std::bind(sum, std::placeholders::_1, std::placeholders::_2);
//auto推导的类型是std::function<int(int, int)>   注意这里的占位符 在placeholder命名空间中 下斜杠+数字表示第几个参数 这里是一个占位符
​
    // 调用绑定后的函数
    int result = addFunc(3, 4);
    std::cout << "Result: " << result << std::endl;  // 输出:Result: 7
    
    return 0;
​
}
3
int sum(double def, int a, int b) {
return a + b;
}
auto addFunc = std::bind(sum,4.0, std::placeholders::_1, std::placeholders::_2);//尽管第一个设置了缺省值 但是占位符还是从一开始的 就是说
result = addFunc(3, 4);  这里的3 就是-1占位符的数据  4就是-2占位符的数据
    4
#include <iostream>
#include <functional>
class MyClass {
public:
void greet(const std::string& name) {
std::cout << "Hello, " << name << "!" << std::endl;
}
};
​
int main() {
    MyClass obj;
​
    // 使用bind函数将成员函数和对象绑定
    auto greetFunc = std::bind(&MyClass::greet, &obj, std::placeholders::_1);
    // 调用绑定后的函数
    greetFunc("Alice");  // 输出:Hello, Alice!
    return 0;
​
}

main函数中,我们创建了一个MyClass对象obj。然后,我们使用std::bind函数将greet成员函数与obj对象绑定,并将std::placeholders::_1作为占位符绑定到函数的参数位置。

&MyClass::greet表示了函数指针,用于指定成员函数greet&obj表示了对象的指针,用于指示成员函数的执行对象。最后,我们可以通过调用greetFunc函数来调用成员函数greet,并传递参数。

需要注意的是,绑定成员函数时需要指定执行的对象,即成员函数的调用对象。在示例中,我们使用了&操作符来获取对象obj的指针。如果需要绑定的成员函数是静态成员函数,则无需指定执行对象。

另外,还可以使用std::mem_fn(下斜杠)来绑定成员函数,它与std::bind类似。但是,相对于std::bindstd::mem_fn在绑定成员函数时更加简洁。使用std::mem_fn的示例代码如下:

#include <iostream>
#include <functional>
​
class MyClass {
public:
    void greet(const std::string& name) {
        std::cout << "Hello, " << name << "!" << std::endl;
    }
};
​
int main() {
    MyClass obj;
​
    // 使用mem_fn函数将成员函数和对象绑定
    auto greetFunc = std::mem_fn(&MyClass::greet);
    
    // 调用绑定后的函数
    greetFunc(obj, "Alice");  // 输出:Hello, Alice!
    
    return 0;
​
}
智能指针

智能指针(Smart Pointer)主要目的是自动管理内存分配和释放,以避免内存泄漏和野指针问题。 智能指针的核心实现方式是使用对象内部的指针来指向实际分配的内存,并在对象销毁时自动释放内存。

C++ 标准库中提供了两种智能指针:std::shared_ptrstd::unique_ptr。还有已经被摒弃的auto_ptr(直接进行资源所有权的转换简单粗暴) 此外,Boost 库中还提供了其他类型的智能指针,如 boost::shared_ptrboost::unique_ptr,它们提供了更高级的内存管理策略。

智能指针的一些主要特点:

  1. 自动内存管理:智能指针会在对象销毁时自动释放内存,避免了内存泄漏的风险。

  2. 对象生命周期跟踪:智能指针可以跟踪对象的生命周期,确保对象在不再需要时被及时释放。

  3. 资源获取与释放的对称性:智能指针的获取和释放是相互对应的,确保资源在使用完毕后被正确释放。

  4. 类型安全:智能指针支持类型安全编程,避免了使用原生指针可能导致的数据类型不一致问题。

  5. 易于扩展:智能指针的实现可以很容易地扩展,以支持自定义的资源管理策略。

Boost库

Boost是一个C++库集合,它提供了很多用于增强C++语言和标准库功能的工具和组件。Boost库是由一群C++爱好者自发组成的社区开发的, 它为C++开发人员提供了许多高质量、经过良好测试和广泛使用的库。Boost的目标是为C++程序员提供更广泛、更有用的库和工具,以提高代码质量、开发效率和可维护性。 Boost库包括各种主题,涵盖了从数据结构、算法、函数对象、指针、元编程、多线程、网络、文件系统、日期时间等各个领域的功能。 其中一些库已被C++标准委员会采纳并成为C++语言的一部分(例如:Boost.Thread被C++11标准引入为std::thread)。

  1. Boost.Smart_Ptr:提供智能指针,例如shared_ptr和weak_ptr。

  2. Boost.Algorithm:提供各种算法和数据结构,例如排序、查找、字符串处理等。

  3. Boost.Functional:提供函数对象和函数合成器,例如bind、lambda等。

  4. Boost.Multi-threading:提供多线程相关的库和工具,例如线程、互斥量、条件变量等。

  5. Boost.Asio:提供异步网络和I/O操作的库,例如网络编程、异步读写等。

  6. Boost.Filesystem:提供对文件系统的操作,例如文件和目录的创建、删除、遍历等。

  7. Boost.Date_Time:提供日期和时间操作的库,例如日期计算、时间戳等。

Boost库的使用需要使用适当的头文件,并链接正确的库文件。 由于Boost库的代码质量和广泛应用,它为C++开发人员提供了可靠和强大的工具,同时也对C++标准的发展起到了重要促进作用。

实现简单智能指针
template <typename T>
class SmartPointer {
public:
    SmartPointer(T* ptr) : ptr_(ptr) {}
    ~SmartPointer() {
        if (ptr_) {
            delete ptr_;
        }
    }
    T& operator*() const {
        return *ptr_;
    }
    T* operator->() const {
        return ptr_;
    }
private:
    T* ptr_;
};
class CustomObject {
public:
    void print() {
        std::cout << "Object printed." << std::endl;
    }
};
​
int main() {
    SmartPointer<CustomObject> smartPtr(new CustomObject());
    smartPtr->print();
    (*smartPtr).print();
    return 0;
}
引用计数

上述示例中的简单智能指针模板类在赋值操作时存在潜在的内存泄漏问题。 这是因为该简单实现没有实现深拷贝和资源管理的复制控制函数(拷贝构造函数和赋值操作符重载函数)。 因此,当智能指针对象进行赋值操作时,只是进行了指针的拷贝而没有对底层对象进行资源的引用计数或共享管理。

为了解决这个问题,我们可以通过引入引用计数的方式来管理智能指针的资源。

#include <iostream>
template <typename T>
class SmartPointer {
public:
    SmartPointer(T* ptr) : ptr_(ptr), refCount_(new unsigned int(1)) {}
    SmartPointer(const SmartPointer<T>& other) : ptr_(other.ptr_), refCount_(other.refCount_) {
        (*refCount_)++;
    }
    ~SmartPointer() {
        release();
    }
​
    SmartPointer<T>& operator=(const SmartPointer<T>& other) {
        if (this != &other) {
            release();
            ptr_ = other.ptr_;
            refCount_ = other.refCount_;
            (*refCount_)++;
        }
        return *this;
    }
    
    T& operator*() const {
        return *ptr_;
    }
    
    T* operator->() const {
        return ptr_;
    }
    
    unsigned int useCount() const {
        return (*refCount_);
    }
​
private:
    void release() {
        if (refCount_ && --(*refCount_) == 0) {
            delete ptr_;
            delete refCount_;
            ptr_ = nullptr;
            refCount_ = nullptr;
        }
    }
    T* ptr_;
    unsigned int* refCount_;
};
​
class CustomObject {
public:
    void print() {
        std::cout << "Object printed." << std::endl;
    }
};
​
int main() {
    SmartPointer<CustomObject> smartPtr1(new CustomObject());
    {
        SmartPointer<CustomObject> smartPtr2 = smartPtr1;
        std::cout << "smartPtr1.useCount(): " << smartPtr1.useCount() << std::endl; // 输出:2
        std::cout << "smartPtr2.useCount(): " << smartPtr2.useCount() << std::endl; // 输出:2
    }
    std::cout << "smartPtr1.useCount(): " << smartPtr1.useCount() << std::endl; // 输出:1
    smartPtr1->print();
    return 0;
}

在这个示例中,我们修改了智能指针模板类,并添加了 refCount_(引用计数)成员变量来跟踪指向对象的指针的引用次数。 另外,我们还实现了拷贝构造函数、赋值操作符重载函数以及 release() 函数。 拷贝构造函数和赋值操作符重载函数在执行时都会递增引用计数,而 release() 函数会在引用计数减为 0 时释放资源。

模拟实现unique_ptr

这个指针禁止拷贝和赋值

template <typename T>
class UniquePtr {
public:
    UniquePtr(T* ptr) : ptr_(ptr) {}
    ~UniquePtr() {
        if (ptr_) {
            delete ptr_;
        }
    }
​
    UniquePtr(const UniquePtr&) = delete;//这里是关键
    UniquePtr& operator=(const UniquePtr&) = delete;
    
    UniquePtr(UniquePtr&& other) : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }
    
    UniquePtr& operator=(UniquePtr&& other) {
        if (this != &other) {
            release();
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }
    
    T& operator*() const {
        return *ptr_;
    }
    
    T* operator->() const {
        return ptr_;
    }
​
private:
    void release() {
        if (ptr_) {
            delete ptr_;
            ptr_ = nullptr;
        }
    }
​
    T* ptr_;
​
};

UniquePtr 类的构造函数接受一个裸指针,并将其存储在 ptr_ 中。析构函数在对象销毁时释放 ptr_ 指向的资源。 为了防止拷贝和赋值操作,我们使用 delete 禁用了拷贝构造函数和赋值操作符的重载。

我们还实现了移动构造函数 UniquePtr(UniquePtr&& other) 和移动赋值操作符 operator=(UniquePtr&& other), 以实现资源所有权在 UniquePtr 对象之间的转移。在移动操作之后,原对象的 ptr_ 成员被置为 nullptr,以避免重复释放资源。

此外,为了与指向资源的裸指针的用法相似,我们还重载了 operator*()operator->() 运算符。

C++标准库中的 std::unique_ptr 还提供了更多的功能和类型特性,比如支持数组类型和自定义删除器等

模拟实现shared_ptr

shared_ptr是C++11中新增的智能指针,用于管理动态分配的资源。它使用引用计数的方式来管理资源的所有权,可以在多个shared_ptr之间共享同一个资源,当最后一个shared_ptr析构时,自动释放该资源。

下面是shared_ptr的模拟实现:

首先,我们需要定义一个结构体,用于存储资源的指针和引用计数:

template <typename T>
struct SharedPtrControlBlock {
    T* ptr;
    int count;
​
    SharedPtrControlBlock(T* p) : ptr(p), count(1) {}
​
    ~SharedPtrControlBlock() {
        delete ptr;
    }
};

然后,我们实现shared_ptr类:

template <typename T>
class shared_ptr {
private:
    SharedPtrControlBlock<T>* controlBlock;
​
public:
    // 默认构造函数
    shared_ptr() : controlBlock(nullptr) {}
​
    // 构造函数
    explicit shared_ptr(T* p) {
        controlBlock = new SharedPtrControlBlock<T>(p);
    }
​
    // 拷贝构造函数
    shared_ptr(const shared_ptr& other) {
        controlBlock = other.controlBlock;
        if (controlBlock) {
            controlBlock->count++;
        }
    }
​
    // 拷贝赋值运算符
    shared_ptr& operator=(const shared_ptr& other) {
        if (this != &other) {
            if (controlBlock) {
                controlBlock->count--;
                if (controlBlock->count == 0) {
                    delete controlBlock;
                }
            }
            controlBlock = other.controlBlock;
            if (controlBlock) {
                controlBlock->count++;
            }
        }
        return *this;
    }
​
    // 析构函数
    ~shared_ptr() {
        if (controlBlock) {
            controlBlock->count--;
            if (controlBlock->count == 0) {
                delete controlBlock;
            }
        }
    }
​
    // 重载解引用操作符
    T& operator*() const {
        return *(controlBlock->ptr);
    }
​
    // 重载箭头操作符
    T* operator->() const {
        return controlBlock->ptr;
    }
​
    // 获取引用计数
    int use_count() const {
        if (controlBlock) {
            return controlBlock->count;
        }
        return 0;
    }
};

使用示例:

shared_ptr<int> p1(new int(5));
shared_ptr<int> p2 = p1;
shared_ptr<int> p3;
p3 = p2;
​
std::cout << "p1 count: " << p1.use_count() << std::endl;  // 输出:p1 count: 3
std::cout << "p2 count: " << p2.use_count() << std::endl;  // 输出:p2 count: 3
std::cout << "p3 count: " << p3.use_count() << std::endl;  // 输出:p3 count: 3

以上就是shared_ptr的简单模拟实现。需要注意的是,这只是一个基本实现,没有对线程安全性进行考虑。在实际使用中,建议使用C++11或更高版本的标准库中的shared_ptr。

注意:再赋值时候要考虑自己给自己赋值的情况

===在有些情况下 时会出现错误的 循环引用问题 这是就会有新的角色出现了

weak_ptr
问题解决

shared_ptr 在 C++11 中通过引入引用计数机制来解决内存管理的问题,同时它也提供了专门的操作来避免循环引用问题。这些操作包括:

  1. weak_ptrshared_ptr 的一个辅助智能指针,它不增加引用计数,从而允许解除循环引用。weak_ptr 可以与 shared_ptr 相关联,通过 shared_ptr::lock 函数来转换成 shared_ptr

  2. std::weak_ptr:这是一个 weakptr 的实例,它与特定的 shared_ptr 相关联,但不会影响引用计数。

  3. std::shared_mutex:这是一个互斥锁,用于保护 shared_ptrweak_ptr 之间的操作,以避免并发访问时的竞态条件。

下面是一个简单的例子,展示了如何使用 weak_ptr 来解决循环引用问题:

#include <iostream>
#include <memory>
​
class MyClass {
public:
    std::shared_ptr<Class> getSharedPtr() {
        return std::shared_ptr<MyClass>(this);
    }
​
    std::weak_ptr<MyClass> getWeakPtr() {
        return std::weak_ptr<MyClass>(this);
    }
};
​
int main() {
    MyClass obj1;
    MyClass obj2;
​
    // 创建 shared_ptr 实例
    std::shared_ptr<MyClass> sp1 = obj1.getSharedPtr();
    std::shared_ptr<MyClass> sp2 = obj2.getSharedPtr();
​
    // 创建 weak_ptr 实例
    std::weak_ptr<MyClass> wp1 = obj1.getWeakPtr();
    std::weak_ptr<MyClass> wp2 = obj2.getWeakPtr();// 使用 weak_ptr 来获取 shared_ptr,避免循环引用
    if (std::shared_ptr<MyClass> shared_obj = wp1.lock()) {
        std::cout << "obj1 is still alive." << std::endl;
    }
​
    if (std::shared_ptr<MyClass>_obj = wp2.lock()) {
        std::cout << "obj2 is still alive." << std::endl;
    }
​
    // 输出 obj1 和 obj2 是否还被引用
    std::cout << "obj1 is referenced: " << sp1.use_count() << std::endl;
    std::cout << "obj2 referenced: " << sp2.use_count() << std::endl;
​
    return 0;
}

在这个例子中,obj1obj2 互相持有对方的 shared_ptr,这会导致循环引用。通过引入 weak_ptr,我们可以避免这个问题。wp1.lock()wp2.lock() 会在 weak_ptr 指向的对象仍然存在时返回 shared_ptr,否则返回空指针。这样,我们就可以在不需要增加引用计数的情况下检查对象是否仍然被引用。

当对象不再被任何 shared_ptr 引用时,它们的引用计数会减少到零,并且在最后一个 shared_ptr 析构时,对象会被自动释放。使用 std::shared_mutex 可以确保在多线程环境中安全地更新和访问 shared_ptrweak_ptr

介绍

std::weak_ptr 是 C++ 标准库中的一个类模板,它是一种相对于 std::shared_ptr 的辅助智能指针。 与 std::shared_ptr 不同,std::weak_ptr 不拥有资源的所有权,它只是对由 std::shared_ptr 管理的资源进行弱引用。

  1. 弱引用:std::weak_ptr 允许我们创建对 std::shared_ptr 管理的资源的非占有性引用。 这意味着 std::weak_ptr 不会增加引用计数,也不影响资源的生命周期。 当 std::shared_ptr 管理的资源被释放时,通过 std::weak_ptr 创建的弱引用对象也会自动失效。

  2. 避免循环引用:在使用 std::shared_ptr 时,存在循环引用的风险,即两个或多个对象互相持有对方的 std::shared_ptr。 这会导致无法释放资源,从而产生内存泄漏。通过使用 std::weak_ptr 来替代某些引用,可以打破循环引用关系,正确释放资源。

  3. 安全地获得 std::shared_ptr:通过调用 std::weak_ptrlock() 方法,可以安全地获取一个有效的 std::shared_ptr 对象。 如果通过 lock() 方法获取到了 std::shared_ptr,说明资源仍然可用,否则返回一个空的 std::shared_ptr

weak_ptr模拟实现

weak_ptr是C++标准库中的一种智能指针,它主要用于解决循环引用问题。当两个智能指针互相引用时,它们无法被安全地删除,因为它们会形成一个循环引用。weak_ptr可以打破这种循环引用,因为它允许我们访问它所指向的对象,但并不增加其引用计数。 以下是一个简单的模拟实现:

#include <iostream>
#include <memory>
​
template<typename T>
class weak_ptr {
public:
    weak_ptr() = default;
    weak_ptr(T* ptr) : ptr_(ptr), ref_count_(ptr) {
        if (ptr_) ++ref_count_;
    }
​
    // 析构函数,如果引用计数为0,则释放对象
    ~weak_ptr() {
        if (--ref_count_ == 0) {
            if (ptr_) delete ptr_;
            ptr_ = nullptr;
        }
    }
​
    // 获取指向对象的指针,但不增加引用计数
    T* get() const { return ptr_; }
    // 检查对象是否仍然存在
    bool valid() const { return ptr_ != nullptr; }
​
private:
    T* ptr_ = nullptr; // 指向对象的指针
    int ref_count_ = 0; // 引用计数
};

使用这个简单的实现时,你需要使用 std::weak_ptr 作为其他智能指针的底层类型。例如:

std::shared_ptr<int> sp = std::make_shared<int>(1); // 创建一个共享指针,指向一个对象
std::weak_ptr<int> wp = sp; // 使用 weak_ptr 替换 shared_ptr 作为底层类型
if (auto sp2 = wp.lock()) { // 使用 get() 函数检查对象是否仍然存在,但不会增加引用计数
    std::cout << "The object still exists." << std::endl;
} else { // 如果对象不存在,删除共享指针,并且尝试释放 weak_ptr 指向的对象
    std::cout << "The object has been deleted." << std::endl;
}

注意这个实现并不是一个完全精确的 std::weak_ptr 实现,因为它省略了一些复杂的功能,如通过引用计数实现并发控制和安全的锁机制

智能指针的定制删除器

在C++中,我们可以使用定制删除器(custom deleter)来管理资源的释放方式。在memory头文件中

定制删除器是一个函数对象,用于在智能指针析构时释放所管理的资源。我们可以自定义删除器来处理特定的资源释放逻辑,例如释放动态分配的内存、关闭文件句柄等。

定制删除器可以作为模板参数传递给智能指针类,或者在创建智能指针时通过构造函数或make_shared函数指定。

#include <iostream>
#include <memory>
​
// 自定义删除器类
class CustomDeleter {
public:
    void operator()(int* p) const {
        std::cout << "Deleting resource: " << *p << std::endl;
        delete p;
    }
};
​
int main() {
    std::shared_ptr<int> p1(new int(5), CustomDeleter()); // 使用自定义删除器创建智能指针
    std::shared_ptr<int> p2 = std::make_shared<int>(10, CustomDeleter()); // 使用自定义删除器创建智能指针
//也可以[](int* p){std::cout << "Deleting resource: " << *p << std::endl; delete p;}
    return 0;
}

在上面的示例中,我们定义了一个名为 CustomDeleter 的删除器类。这个类重载了括号运算符,当智能指针析构时调用该运算符。在 operator() 函数中,我们输出待释放资源的值,并使用 delete 关键字释放指向 int 的动态分配内存。

在创建智能指针时,我们传递了 CustomDeleter 的一个实例作为删除器参数。当智能指针析构时,就会调用 CustomDeleter 的括号运算符函数来释放资源。

需要注意的是,删除器的函数参数类型必须与智能指针管理资源的指针类型相匹配。在示例中,我们的删除器函数接受 int* 类型的指针作为参数,因此我们创建了shared_ptr<int>类型的智能指针。

定制删除器下的shared_ptr
#include <iostream>
​
template<typename T, typename Deleter = std::default_delete<T>>
class shared_ptr {
public:
    // 构造函数
    explicit shared_ptr(T* p = nullptr, Deleter d = Deleter())
        : ptr_(p), count_(new int(1)), deleter_(d) {}
​
    // 拷贝构造函数
    shared_ptr(const shared_ptr& other)
        : ptr_(other.ptr_), count_(other.count_), deleter_(other.deleter_) {
        (*count_)++;
    }
    
    // 析构函数
    ~shared_ptr() {
        if (--(*count_) == 0) {
            deleter_(ptr_);
            delete count_;
        }
    }
    
    // 拷贝赋值运算符
    shared_ptr& operator=(const shared_ptr& other) {
        if (this != &other) {
            if (--(*count_) == 0) {
                deleter_(ptr_);
                delete count_;
            }
            ptr_ = other.ptr_;
            count_ = other.count_;
            deleter_ = other.deleter_;
            (*count_)++;
        }
        return *this;
    }
    
    // 重载解引用操作符
    T& operator*() const { return *ptr_; }
    
    // 重载箭头操作符
    T* operator->() const { return ptr_; }
    
    // 获取引用计数
    int use_count() const { return *count_; }
​
private:
    T* ptr_;
    int* count_;
    Deleter deleter_;
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值