C++语言模板类对原生指针的封装与模拟

简介: C++|智能指针的智能性和指针性:模板类对原生指针的封装与模拟

Pointers are used for accessing the resources which are external to the program – like heap memory. So, for accessing the heap memory (if anything is created inside heap memory), pointers are used. When accessing any external resource we just use a copy of the resource. If we make any change to it, we just change it in the copied version. But, if we use a pointer to the resource, we’ll be able to change the original resource.

指针用于访问程序外部的资源,如堆内存。因此,为了访问堆内存(如果在堆内存中创建了任何内容),使用指针。当访问任何外部资源时,我们只使用该资源的一个副本。如果我们对其进行任何更改,我们只需在复制版本中进行更改。但是,如果我们使用指向资源的指针,我们将能够更改原始资源。

1 Problems with Normal Pointers
Take a look at the code below.

include

using namespace std;
class Rectangle {
private:
int length;
int breadth;
};
void fun()
{
// By taking a pointer p and
// dynamically creating object
// of class rectangle
Rectangle* p = new Rectangle();
}
int main()
{
// Infinite Loop
while(1) {
fun();
}
}//代码效果参考:http://www.zidongmutanji.com/bxxx/583062.html

In function fun, it creates a pointer that is pointing to the Rectangle object. The object Rectangle contains two integers, length and breadth. When the function fun ends, p will be destroyed as it is a local variable. But, the memory it consumed won’t be deallocated because we forgot to use delete p; at the end of the function. That means the memory won’t be free to be used by other resources. But, we don’t need the variable anymore, but we need the memory.

在fun函数中,它创建一个指向矩形对象的指针。对象矩形包含两个整数,长度和宽度。当函数fun结束时,p将被销毁,因为它是一个局部变量。但是,它消耗的内存不会被释放,因为我们忘记了使用delete p;在函数的末尾。这意味着内存将不能被其他资源使用。但是,我们不再需要这个变量,而是需要内存。

In function main, fun is called in an infinite loop. That means it’ll keep creating p. It’ll allocate more and more memory but won’t free them as we didn’t deallocate it. The memory that’s wasted can’t be used again. Which is a memory leak. The entire heap memory may become useless for this reason. C++11 comes up with a solution to this problem, Smart Pointer.

在函数main中,fun在无限循环中调用。这意味着它将继续创建p。它将分配越来越多的内存,但不会释放它们,因为我们没有释放它。浪费的内存不能再使用。这是内存泄漏。因此,整个堆内存可能会变得无用。C++11为这个问题提出了一个解决方案,即智能指针。

2 Introduction of Smart Pointers
As we’ve known unconsciously not deallocating a pointer causes a memory leak that may lead to crash of the program. Languages Java, C# has Garbage Collection Mechanisms to smartly deallocate unused memory to be used again. The programmer doesn’t have to worry about any memory leak. C++11 comes up with its own mechanism that’s Smart Pointer. When the object is destroyed it frees the memory as well. So, we don’t need to delete it as Smart Pointer does will handle it.

正如我们所知,不释放指针会导致内存泄漏,从而导致程序崩溃。在Java语言中,C#具有垃圾收集机制,可以巧妙地释放未使用的内存以供再次使用。程序员不必担心任何内存泄漏。C++11提出了自己的机制,即智能指针。当对象被销毁时,它也会释放内存。所以,我们不需要删除它(因为智能指针会处理它)。

A Smart Pointer is a wrapper class over a pointer with an operator like * and -> overloaded. The objects of the smart pointer class look like normal pointers. But, unlike Normal Pointers it can deallocate and free destroyed object memory.

智能指针是指针上的包装类,带有*和->重载运算符。智能指针类的对象看起来像普通指针。但是,与普通指针不同,它可以释放并释放被破坏的对象内存。

The idea is to take a class with a pointer, destructor and overloaded operators like * and ->. Since the destructor is automatically called when an object goes out of scope, the dynamically allocated memory would automatically be deleted (or reference count can be decremented). Consider the following simple SmartPtr class.

其思想是采用一个带有指针、析构函数和重载运算符(如*和->)的类。由于当对象超出作用域时会自动调用析构函数,因此动态分配的内存将自动删除(或者可以减少引用计数)。考虑以下简单的SmartPtr类。

include

using namespace std;
class SmartPtr {
int ptr; // Actual pointer
public:
// Constructor: Refer https:// www.geeksforgeeks.org/g-fact-93/
// for use of explicit keyword
explicit SmartPtr(int
p = NULL) { ptr = p; }
// Destructor
~SmartPtr() { delete (ptr); }
// Overloading dereferencing operator
int& operator() { return ptr; }
};
int main()
{
SmartPtr ptr(new int());
ptr = 20;
cout <<
ptr; // 20
// We don't need to call delete ptr: when the object
// ptr goes out of scope, the destructor for it is automatically
// called and destructor does delete ptr.
return 0;
}
This only works for int. So, we’ll have to create Smart Pointer for every object? No, there’s a solution, Template. In the code below as you can see T can be of any type. Read more about Template here.

这只适用于int。所以,我们必须为每个对象创建智能指针?不,有一个解决方案,模板。在下面的代码中,您可以看到T可以是任何类型。在此处阅读有关模板的更多信息。
//代码效果参考:http://www.zidongmutanji.com/bxxx/396322.html

include

using namespace std;
// A generic smart pointer class
template
class SmartPtr {
T ptr; // Actual pointer
public:
// Constructor
explicit SmartPtr(T
p = NULL) { ptr = p; }
// Destructor
~SmartPtr() { delete (ptr); }
// Overloading dereferencing operator
T& operator() { return ptr; }
// Overloading arrow operator so that
// members of T can be accessed
// like a pointer (useful if T represents
// a class or struct or union type)
T operator->() { return ptr; }
};
int main()
{
SmartPtr ptr(new int());
ptr = 20;
cout << *ptr; // 20
return 0;
}
Note: Smart pointers are also useful in the management of resources, such as file handles or network sockets.

注意:智能指针在资源管理中也很有用,例如文件句柄或网络套接字。

3 Types of Smart Pointers
3.1 unique_ptr

unique_ptr stores one pointer only. We can assign a different object by removing the current object from the pointer. Notice the code below. First, the unique_pointer is pointing to P1. But, then we remove P1 and assign P2 so the pointer now points to P2.

unique_ ptr仅存储一个指针。我们可以通过从指针中移除当前对象来分配不同的对象。请注意下面的代码。首先,首先,unique_pointer指针指向P1。但是,然后我们移除P1并分配P2,因此指针现在指向P2。

code demo:

include

using namespace std;

include

class Rectangle {
int length;
int breadth;
public:
Rectangle(int l, int b){
length = l;
breadth = b;
}
int area(){
return length breadth;
}
};
int main(){
unique_ptr P1(new Rectangle(10, 5));
cout << P1->area() << endl; // This'll print 50
// unique_ptr P2(P1);
unique_ptr P2;
P2 = move(P1);
// This'll print 50
cout << P2->area() << endl;
// cout<area()<<endl;
return 0;
}
/

Output:
50
50
*/
std::unique_ptr was developed in C++11 as a replacement for std::auto_ptr.unique_ptr is a new facility with similar functionality, but with improved security (no fake copy assignments), added features (deleters) and support for arrays. It is a container for raw pointers. It explicitly prevents copying of its contained pointer as would happen with normal assignment i.e. it allows exactly one owner of the underlying pointer.

std::unique_ptr是在C++11中开发的,作为std::auto_ptr的替代,是一种具有类似功能的新工具,但具有改进的安全性(无假拷贝分配)、添加的功能(删除器)和对数组的支持。它是原始指针的容器。它显式地防止复制其包含的指针,就像正常分配一样,也就是说,它只允许基本指针的一个所有者。

3.2 shared_ptr

By using shared_ptr more than one pointer can point to this one object at a time and it’ll maintain a Reference Counter using use_count() method.

通过使用shared_ptr,一次可以有多个指针指向这个对象,它将使用use_count()方法维护一个引用计数器。

code demo:
//代码效果参考:http://www.zidongmutanji.com/zsjx/532281.html

include

using namespace std;

include

class Rectangle {
int length;
int breadth;
public:
Rectangle(int l, int b)
{
length = l;
breadth = b;
}
int area()
{
return length breadth;
}
};
int main()
{
shared_ptr P1(new Rectangle(10, 5));
// This'll print 50
cout << P1->area() << endl;
shared_ptr P2;
P2 = P1;
// This'll print 50
cout << P2->area() << endl;
// This'll now not give an error,
cout << P1->area() << endl;
// This'll also print 50 now
// This'll print 2 as Reference Counter is 2
cout << P1.use_count() << endl;
return 0;
}
/
Output:
50
50
50
2
*/
3.3 weak_ptr
It’s much more similar to shared_ptr except it’ll not maintain a Reference Counter. In this case, a pointer will not have a stronghold on the object. The reason is if suppose pointers are holding the object and requesting for other objects then they may form a Deadlock.

它与shared_ptr更相似,只是它不维护引用计数器。在这种情况下,指针在对象上没有据点。原因是如果假设指针持有该对象并请求其他对象,则它们可能会形成死锁。

A weak_ptr is created as a copy of shared_ptr. It provides access to an object that is owned by one or more shared_ptr instances but does not participate in reference counting. The existence or destruction of weak_ptr has no effect on the shared_ptr or its other copies. It is required in some cases to break circular references between shared_ptr instances.

weakptr被创建为shared ptr的副本。它提供对一个或多个shared_ ptr实例拥有的对象的访问,但不参与引用计数。weakptr的存在或销毁对shared ptr或其其他副本没有影响。在某些情况下,需要中断shared_ ptr实例之间的循环引用。

Cyclic Dependency (Problems with shared_ptr): Let’s consider a scenario where we have two classes A and B, both have pointers to other classes. So, it’s always like A is pointing to B and B is pointing to A. Hence, use_count will never reach zero and they never get deleted.

循环依赖(shared_ptr的问题):让我们考虑一个场景,其中我们有两个类a和B,它们都有指向其他类的指针。所以,A总是指向B,B总是指向A。因此,use_count永远不会达到零,它们永远不会被删除。

This is the reason we use weak pointers(weak_ptr) as they are not reference counted. So, the class in which weak_ptr is declared doesn’t have a stronghold of it i.e. the ownership isn’t shared, but they can have access to these objects.

这就是我们使用弱指针(weak_ptr)的原因,因为它们不是引用计数的。因此,声明weak_ptrr的类没有它的据点,即所有权不共享,但它们可以访问这些对象。

So, in case of shared_ptr because of cyclic dependency use_count never reaches zero which is prevented using weak_ptr, which removes this problem by declaring A_ptr as weak_ptr, thus class A does not own it, only have access to it and we also need to check the validity of object as it may go out of scope.

因此,在shared_ptr的情况下,由于循环依赖性,use_count永远不会达到零,这是通过使用weak_ptr来防止的,它通过将Aptr声明为weak ptr来消除这个问题,因此类A不拥有它,只能访问它,我们还需要检查对象的有效性,因为它可能超出作用域。

总结一下,原生指针指向堆内存或其它资源可能存在的问题:

① 忘记delete,造成内存泄露;

② delete后还在使用;

③ 重复delete;

资源被指针指向,可以区分两种情况:

① 只有一个指针指向它(没有copy,但可以move)

② 有多个指针指向它(有copy)

核心就是所有权(ownership)的问题。

STL由此引入了三类模板类智能指针:

① unique_ptr<> // not copy, can move

② shared_ptr<> // eference counting

③ 为避免循环引用的问题,引入了weak_ptr<>

相关文章
|
3月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
91 0
|
3月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
169 0
|
5月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
177 12
|
6月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
7月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
6月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
129 16
|
7月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
6月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
6月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
6月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
349 6