Don Box:Smart Pointers : What , Why , Which?

本文围绕C++智能指针展开,介绍其需具备指针接口且比普通指针更智能,能解决指针和内存管理常见问题。阐述使用智能指针可减少错误、保证异常安全、实现垃圾回收、提高效率及适用于STL容器等原因,还给出不同场景下智能指针的选择建议。
部署运行你感兴趣的模型镜像

What are they?

Smart pointers are objects that look and feel like pointers, but are smarter. What does this mean?

To look and feel like pointers, smart pointers need to have the same interface that pointers do: they need to support pointer operations like dereferencing (operator *) and indirection (operator ->). An object that looks and feels like something else is called a proxy object, or just proxy. The proxy pattern and its many uses are described in the books Design Patterns and Pattern Oriented Software Architecture.

To be smarter than regular pointers, smart pointers need to do things that regular pointers don't. What could these things be? Probably the most common bugs in C++ (and C) are related to pointers and memory management: dangling pointers, memory leaks, allocation failures and other joys. Having a smart pointer take care of these things can save a lot of aspirin...

The simplest example of a smart pointer is auto_ptr, which is included in the standard C++ library. You can find it in the header <memory>, or take a look at Scott Meyers' auto_ptr implementation. Here is part of auto_ptr's implementation, to illustrate what it does:

template <class T> class auto_ptr
{
    T* ptr;
public:
    explicit auto_ptr(T* p = 0) : ptr(p) {}
    ~auto_ptr()                 {delete ptr;}
    T& operator*()              {return *ptr;}
    T* operator->()             {return ptr;}
    // ...
};
As you can see, auto_ptr is a simple wrapper around a regular pointer. It forwards all meaningful operations to this pointer (dereferencing and indirection). Its smartness in the destructor: the destructor takes care of deleting the pointer.

For the user of auto_ptr, this means that instead of writing:

void foo()
{
    MyClass* p(new MyClass);
    p->DoSomething();
    delete p;
}
You can write:
void foo()
{
    auto_ptr<MyClass> p(new MyClass);
    p->DoSomething();
}
And trust p to cleanup after itself.

What does this buy you? See the next section.

Why would I use them?

Obviously, different smart pointers offer different reasons for use. Here are some common reasons for using smart pointers in C++.

Why: Less bugs

Automatic cleanup. As the code above illustrates, using smart pointers that clean after themselves can save a few lines of code. The importance here is not so much in the keystrokes saved, but in reducing the probability for bugs: you don't need to remember to free the pointer, and so there is no chance you will forget about it.

Automatic initialization. Another nice thing is that you don't need to initialize the auto_ptr to NULL, since the default constructor does that for you. This is one less thing for the programmer to forget.

Dangling pointers. A common pitfall of regular pointers is the dangling pointer: a pointer that points to an object that is already deleted. The following code illustrates this situation:

MyClass* p(new MyClass);
MyClass* q = p;
delete p;
p->DoSomething();   // Watch out! p is now dangling!
p = NULL;           // p is no longer dangling
q->DoSomething();   // Ouch! q is still dangling!
For auto_ptr, this is solved by setting its pointer to NULL when it is copied:
template <class T>
auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs)
{
    if (this != &rhs) {
        delete ptr;
        ptr = rhs.ptr;
        rhs.ptr = NULL;
    }
    return *this;
}
Other smart pointers may do other things when they are copied. Here are some possible strategies for handling the statement q = p, where p and q are smart pointers:
  • Create a new copy of the object pointed by p, and have q point to this copy. This strategy is implemented in copied_ptr.h.
  • Ownership transfer: Let both p and q point to the same object, but transfer the responsibility for cleaning up ("ownership") from p to q. This strategy is implemented in owned_ptr.h.
  • Reference counting: Maintain a count of the smart pointers that point to the same object, and delete the object when this count becomes zero. So the statement q = p causes the count of the object pointed by p to increase by one. This strategy is implemented in counted_ptr.h. Scott Meyers offers another reference counting implementation in his book More Effective C++.
  • Reference linking: The same as reference counting, only instead of a count, maintain a circular doubly linked list of all smart pointers that point to the same object. This strategy is implemented in linked_ptr.h.
  • Copy on write: Use reference counting or linking as long as the pointed object is not modified. When it is about to be modified, copy it and modify the copy. This strategy is implemented in cow_ptr.h.
All these techniques help in the battle against dangling pointers. Each has each own benefits and liabilities. The Which section of this article discusses the suitability of different smart pointers for various situations.

Why: Exception Safety

Let's take another look at this simple example:
void foo()
{
    MyClass* p(new MyClass);
    p->DoSomething();
    delete p;
}
What happens if DoSomething() throws an exception? All the lines after it will not get executed and p will never get deleted! If we're lucky, this leads only to memory leaks. However, MyClass may free some other resources in its destructor (file handles, threads, transactions, COM references, mutexes) and so not calling it my cause severe resource locks.

If we use a smart pointer, however, p will be cleaned up whenever it gets out of scope, whether it was during the normal path of execution or during the stack unwinding caused by throwing an exception.

But isn't it possible to write exception safe code with regular pointers? Sure, but it is so painful that I doubt anyone actually does this when there is an alternative. Here is what you would do in this simple case:

void foo()
{
    MyClass* p;
    try {
        p = new MyClass;
        p->DoSomething();
        delete p;
    }
    catch (...) {
        delete p;
        throw;
    }
}
Now imagine what would happen if we had some if's and for's in there...

Why: Garbage collection

Since C++ does not provide automatic garbage collection like some other languages, smart pointers can be used for that purpose. The simplest garbage collection scheme is reference counting or reference linking, but it is quite possible to implement more sophisticated garbage collection schemes with smart pointers. For more information see the garbage collection FAQ.

Why: Efficiency

Smart pointers can be used to make more efficient use of available memory and to shorten allocation and deallocation time.

A common strategy for using memory more efficiently is copy on write (COW). This means that the same object is shared by many COW pointers as long as it is only read and not modified. When some part of the program tries to modify the object ("write"), the COW pointer creates a new copy of the object and modifies this copy instead of the original object. The standard string class is commonly implemented using COW semantics (see the <string> header).

string s("Hello");

string t = s;       // t and s point to the same buffer of characters

t += " there!";     // a new buffer is allocated for t before
                    // appending " there!", so s is unchanged.

Optimized allocation schemes are possible when you can make some assumptions about the objects to be allocated or the operating environment. For example, you may know that all the objects will have the same size, or that they will all live in a single thread. Although it is possible to implement optimized allocation schemes using class-specific new and delete operators, smart pointers give you the freedom to choose whether to use the optimized scheme for each object, instead of having the scheme set for all objects of a class. It is therefore possible to match the allocation scheme to different operating environments and applications, without modifying the code for the entire class.

Why: STL containers

The C++ standard library includes a set of containers and algorithms known as the standard template library (STL). STL is designed to be generic (can be used with any kind of object) and efficient (does not incur time overhead compared to alternatives). To achieve these two design goals, STL containers store their objects by value. This means that if you have an STL container that stores objects of class Base, it cannot store of objects of classes derived from Base.
class Base { /*...*/ };
class Derived : public Base { /*...*/ };

Base b;
Derived d;
vector<Base> v;

v.push_back(b); // OK
v.push_back(d); // error
What can you do if you need a collection of objects from different classes? The simplest solution is to have a collection of pointers:
vector<Base*> v;

v.push_back(new Base);      // OK
v.push_back(new Derived);   // OK too

// cleanup:
for (vector<Base*>::iterator i = v.begin(); i != v.end(); ++i)
    delete *i;
The problem with this solution is that after you're done with the container, you need to manually cleanup the objects stored in it. This is both error prone and not exception safe.

Smart pointers are a possible solution, as illustrated below. (An alternative solution is a smart container, like the one implemented in pointainer.h.)

vector< linked_ptr<Base> > v;
v.push_back(new Base);      // OK
v.push_back(new Derived);   // OK too

// cleanup is automatic
Since the smart pointer automatically cleans up after itself, there is no need to manually delete the pointed objects.

Note: STL containers may copy and delete their elements behind the scenes (for example, when they resize themselves). Therefore, all copies of an element must be equivalent, or the wrong copy may be the one to survive all this copying and deleting. This means that some smart pointers cannot be used within STL containers, specifically the standard auto_ptr and any ownership-transferring pointer. For more info about this issue, see C++ Guru of the Week #25.

Which one should I use?

Are you confused enough? Well, this summary should help.

Which: Local variables

The standard auto_ptr is the simplest smart pointer, and it is also, well, standard. If there are no special requirements, you should use it. For local variables, it is usually the right choice.

Which: Class members

Although you can use auto_ptr as a class member (and save yourself the trouble of freeing objects in the destructor), copying one object to another will nullify the pointer, as illustrated Below.
class MyClass
{
    auto_ptr<int> p;
    // ...
};

MyClass x;
// do some meaningful things with x
MyClass y = x; // x.p now has a NULL pointer
Using a copied pointer instead of auto_ptr solves this problem: the copied object (y) gets a new copy of the member.

Note that using a reference counted or reference linked pointer means that if y changes the member, this change will also affect x! Therefore, if you want to save memory, you should use a COW pointer and not a simple reference counted/linked pointer.

Which: STL containers

As explained above, using garbage-collected pointers with STL containers lets you store objects from different classes in the same container.

It is important to consider the characteristics of the specific garbage collection scheme used. Specifically, reference counting/linking can leak in the case of circular references (i.e., when the pointed object itself contains a counted pointer, which points to an object that contains the original counted pointer). Its advantage over other schemes is that it is both simple to implement and deterministic. The deterministic behavior may be important in some real time systems, where you cannot allow the system to suddenly wait while the garbage collector performs its housekeeping duties.

Generally speaking, there are two ways to implement reference counting: intrusive and non-intrusive. Intrusive means that the pointed object itself contains the count. Therefore, you cannot use intrusive reference counting with 3-rd party classes that do not already have this feature. You can, however, derive a new class from the 3-rd party class and add the count to it. Non-intrusive reference counting requires an allocation of a count for each counted object. The counted_ptr.h is an example of non-intrusive reference counting.

Reference linking does not require any changes to be made to the pointed objects, nor does it require any additional allocations. A reference linked pointer takes a little more space than a reference counted pointer - just enough to store one or two more pointers.

Both reference counting and reference linking require using locks if the pointers are used by more than one thread of execution.

Which: Explicit ownership transfer

Sometimes, you want to receive a pointer as a function argument, but keep the ownership of this pointer (i.e. the control over its lifetime) to yourself. One way to do this is to use consistent naming-conventions for such cases. Taligent's Guide to Designing Programs recommends using "adopt" to mark that a function adopts ownership of a pointer.

Using an owned pointer as the function argument is an explicit statement that the function is taking ownership of the pointer.

Which: Big objects

If you have objects that take a lot of space, you can save some of this space by using COW pointers. This way, an object will be copied only when necessary, and shared otherwise. The sharing is implemented using some garbage collection scheme, like reference counting or linking.

Which: Summary

For this:Use that:
Local variablesauto_ptr
Class membersCopied pointer
STL ContainersGarbage collected pointer (e.g. reference counting/linking)
Explicit ownership transferOwned pointer
Big objectsCopy on write

Conclusion

Smart pointers are useful tools for writing safe and efficient code in C++. Like any tool, they should be used with appropriate care, thought and knowledge. For a comprehensive and in depth analysis of the issues concerning smart pointers, I recommend reading Andrei Alexandrescu's chapter about smart pointers in his book Modern C++ Design.

Feel free to use my own smart pointers in your code, and do tell me if you are having any problems with them.
The Boost C++ libraries include some smart pointers, which are more rigorously tested and actively maintained. Do try them first, if they are appropriate for your needs.

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

代码转载自:https://pan.quark.cn/s/a4b39357ea24 本文重点阐述了利用 LabVIEW 软件构建的锁相放大器的设计方案及其具体实施流程,并探讨了该设备在声波相位差定位系统中的实际运用情况。 锁相放大器作为一项基础测量技术,其核心功能在于能够精确锁定微弱信号的频率参数并完成相关测量工作。 在采用 LabVIEW 软件开发的锁相放大器系统中,通过计算测量信号与两条参考信号之间的互相关函数,实现对微弱信号的频率锁定,同时输出被测信号的幅值信息。 虚拟仪器技术是一种基于计算机硬件平台的仪器系统,其显著特征在于用户可以根据实际需求自主设计仪器功能,配备虚拟化操作界面,并将测试功能完全由专用软件程序实现。 虚拟仪器系统的基本架构主要由计算机主机、专用软件程序以及硬件接口模块等核心部件构成。 虚拟仪器最突出的优势在于其功能完全取决于软件编程,用户可以根据具体应用场景灵活调整系统功能参数。 在基于 LabVIEW 软件开发的锁相放大器系统中,主要运用 LabVIEW 软件平台完成锁相放大器功能的整体设计。 LabVIEW 作为一个图形化编程环境,能够高效地完成虚拟仪器的开发工作。 借助 LabVIEW 软件,可以快速构建锁相放大器的用户操作界面,并且可以根据实际需求进行灵活调整和功能扩展。 锁相放大器系统的关键构成要素包括测量信号输入通道、参考信号输入通道、频率锁定处理单元以及信号幅值输出单元。 测量信号是系统需要检测的对象,参考信号则用于引导系统完成对测量信号的频率锁定。 频率锁定处理单元负责实现测量信号的锁定功能,信号幅值输出单元则负责输出被测信号的幅值大小。 在锁相放大器的实际实现过程中,系统采用了双路参考信号输入方案来锁定测量信号。 通过分析两路参考信号之间的相...
边缘计算环境中基于启发式算法的深度神经网络卸载策略(Matlab代码实现)内容概要:本文介绍了在边缘计算环境中,利用启发式算法实现深度神经网络任务卸载的策略,并提供了相应的Matlab代码实现。文章重点探讨了如何通过合理的任务划分与调度,将深度神经网络的计算任务高效地卸载到边缘服务器,从而降低终端设备的计算负担、减少延迟并提高整体系统效率。文中涵盖了问题建模、启发式算法设计(如贪心策略、遗传算法、粒子群优化等可能的候选方法)、性能评估指标(如能耗、延迟、资源利用率)以及仿真实验结果分析等内容,旨在为边缘智能计算中的模型推理优化提供可行的技术路径。; 适合人群:具备一定编程基础,熟悉Matlab工具,从事边缘计算、人工智能、物联网或智能系统优化方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究深度神经网络在资源受限设备上的部署与优化;②探索边缘计算环境下的任务卸载机制与算法设计;③通过Matlab仿真验证不同启发式算法在实际场景中的性能表现,优化系统延迟与能耗。; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,重点关注算法实现细节与仿真参数设置,同时可尝试复现并对比不同启发式算法的效果,以深入理解边缘计算中DNN卸载的核心挑战与解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值