CppCoreGuidelines R: Resource management
The fundamental aim is to ensure that we don't leak any resources and that we don't hold a resource longer than we need to. An entity that is responsible for releasing a resource is called an owner.
NOTE:
1、"don't leak any resources"需要 strong exception safety
2、后续很多内容都是围绕 "don't leak any resources"而展开
3、resource ownership: 这里提出了**owner**的概念
1、Resource management rule summary:
R.2: In interfaces, use raw pointers to denote individual objects (only)
R.3: A raw pointer (a T*
) is non-owning
R.4: A raw reference (a T&
) is non-owning
R.5: Prefer scoped objects, don't heap-allocate unnecessarily
R.6: Avoid non-const
global variables
2、Allocation and deallocation rule summary:
R.10: Avoid malloc()
and free()
R.11: Avoid calling new
and delete
explicitly
R.12: Immediately give the result of an explicit resource allocation to a manager object
R.13: Perform at most one explicit resource allocation in a single expression statement
R.14: Avoid []
parameters, prefer span
R.15: Always overload matched allocation/deallocation pairs
3、Smart pointer rule summary
NOTE: 这部分放到了smart pointer章节
R.1: Manage resources automatically using resource handles and RAII (Resource Acquisition Is Initialization)
Reason
To avoid leaks and the complexity of manual resource management. C++'s language-enforced constructor/destructor symmetry mirrors the symmetry inherent in resource acquire/release function pairs such as fopen
/fclose
, lock
/unlock
, and new
/delete
. Whenever you deal with a resource that needs paired acquire/release function calls, encapsulate that resource in an object that enforces pairing for you -- acquire the resource in its constructor, and release it in its destructor.
NOTE:
总结的非常好。
1、RAII idiom、resource handle
2、object-based resource management
最终目的:
1、保证strong exception safety
2、no resource leakage
3、simplify
4、明确ownership,让代码容易理解、维护,关于这一点,参见:
Example, bad
NOTE: 原因给出的例子一般,比较容易理解
Example
Consider:
void send(unique_ptr<X> x, string_view destination) // x owns the X
{
Port port{destination}; // port owns the PortHandle
lock_guard<mutex> guard{my_mutex}; // guard owns the lock
// ...
send(port, x);
// ...
} // automatically unlocks my_mutex and deletes the pointer in x
Now all resource cleanup is automatic, performed once on all paths whether or not there is an exception. As a bonus, the function now advertises that it takes over ownership of the pointer.
Note
Where a resource is "ill-behaved" in that it isn't represented as a class with a destructor, wrap it in a class or use finally
NOTE:
1、上面这段话中的"ill-behaved"如何理解?
2、
finally
其实就是guard
See also: RAII
R.2: In interfaces, use raw pointers to denote individual objects (only)
NOTE:
1、原文的这段话,其实说的不够直接,它想表达是: 在interface中,不要使用raw pointer来数组表示array: 不要使用pointer + length的模式,而是使用
vector
或者span
;仅仅使用raw pointer来表示单个object(individual object )
Reason
Arrays are best represented by a container type (e.g., vector
(owning)) or a span
(non-owning). Such containers and views hold sufficient information to do range checking.
Example, bad
void f(int* p, int n) // n is the number of elements in p[]
{
// ...
p[2] = 7; // bad: subscript raw pointer
// ...
}
NOTE:
1、这是我们常常使用的方式
The compiler does not read comments, and without reading other code you do not know whether p
really points to n
elements. Use a span
instead.
Example
void g(int* p, int fmt) // print *p using format #fmt
{
// ... uses *p and p[0] only ...
}
Exception
C-style strings are passed as single pointers to a zero-terminated sequence of characters. Use zstring
rather than char*
to indicate that you rely on that convention.
R.3: A raw pointer (a T*
) is non-owning
Reason
There is nothing (in the C++ standard or in most code) to say otherwise and most raw pointers are non-owning. We want owning pointers identified so that we can reliably and efficiently delete the objects pointed to by owning pointers.
NOTE: 翻译如下:
"没有什么(在c++标准或大多数代码中)不是这样说的,大多数原始指针是非拥有的。 我们希望标识拥有指针的对象,以便能够可靠和有效地删除由拥有指针所指向的对象。"
意思是:
1、默认情况下,在C++中,raw pointer表达的是"not owning semantic",这是在 C++中约定俗成的
2、为了"明确Resource ownership",可以使用smart pointer,并且它还是提供了RAII
Example
void f()
{
int* p1 = new int{7}; // bad: raw owning pointer
auto p2 = make_unique<int>(7); // OK: the int is owned by a unique pointer
// ...
}
The unique_ptr
protects against leaks by guaranteeing the deletion of its object (even in the presence of exceptions). The T*
does not.
Example
template<typename T>
class X {
public:
T* p; // bad: it is unclear whether p is owning or not
T* q; // bad: it is unclear whether q is owning or not
// ...
};
We can fix that problem by making ownership explicit:
template<typename T>
class X2 {
public:
owner<T*> p; // OK: p is owning
T* q; // OK: q is not owning
// ...
};
NOTE:
1、使semantic清晰
Note
owner<T*>
has no default semantics beyond T*
. It can be used without changing any code using it and without affecting ABIs. It is simply an indicator to programmers and analysis tools. For example, if an owner<T*>
is a member of a class, that class better have a destructor that delete
s it.
Example, bad
NOTE:
1、使用 resource return idiom
In addition to suffering from the problem from leak, this adds a spurious allocation and deallocation operation, and is needlessly verbose. If Gadget
is cheap to move out of a function (i.e., is small or has an efficient move operation), just return it "by value" (see "out" return values):
NOTE:
1、" If
Gadget
is cheap to move out of a function (i.e., is small or has an efficient move operation), just return it "by value"" 让我想起了:Optimization-in-function-return
章节、Move-and-return
章节、two-stage overload resolution
Gadget make_gadget(int n)
{
Gadget g{n};
// ...
return g;
}
Note
This rule applies to factory functions.
Note
If pointer semantics are required (e.g., because the return type needs to refer to a base class of a class hierarchy (an interface)), return a "smart pointer."
R.4: A raw reference (a T&
) is non-owning
NOTE: 和前面类似
R.5: Prefer scoped objects, don't heap-allocate unnecessarily
NOTE:
1、比较好理解
R.10: Avoid malloc()
and free()
Reason
malloc()
and free()
do not support construction and destruction, and do not mix well with new
and delete
.
NOTE:
1、要理解上面这段话,需要对于C++ object lifetime有一个完整的认知,在下面章节对这个topic进行了讨论:
a、
Lifetime-and-storage-duration
b、
C++\Language-reference\Classes\Lifetime
其实,我们可以这样简单理解:
malloc
和free
是为C而设计的,new
和delete
是为C++而设计的
Example
#include <string>
#include "stdlib.h"
using namespace std;
class Record
{
int id;
string name;
// ...
};
void use()
{
// p1 might be nullptr
// *p1 is not initialized; in particular,
// that string isn't a string, but a string-sized bag of bits
Record *p1 = static_cast<Record*>(malloc(sizeof(Record)));
auto p2 = new Record;
// unless an exception is thrown, *p2 is default initialized
auto p3 = new (nothrow) Record;
// p3 might be nullptr; if not, *p3 is default initialized
// ...
delete p1; // error: cannot delete object allocated by malloc()
free(p2); // error: cannot free() object allocated by new
}
int main()
{
use();
}
// g++ -Wall -pedantic test.cpp && ./a.out
NOTE:
运行后,core dump了
Exception
There are applications and sections of code where exceptions are not acceptable. Some of the best such examples are in life-critical hard-real-time code. Beware that many bans on exception use are based on superstition (bad) or by concerns for older code bases with unsystematic resource management (unfortunately, but sometimes necessary). In such cases, consider the nothrow
versions of new
.
NOTE: 翻译如下:
"有些应用程序和代码段是不能接受异常的。 其中一些最好的例子是关键的硬实时代码。 要注意,许多禁止异常使用的做法都是基于迷信(不好)或对具有非系统资源管理的旧代码库的关注(不幸的是,但有时是必要的)。 在这种情况下,考虑一下“new”的“nothrow”版本。"
Enforcement
Flag explicit use of malloc
and free
.
R.11: Avoid calling new
and delete
explicitly
Reason
The pointer returned by new
should belong to a resource handle (that can call delete
). If the pointer returned by new
is assigned to a plain/naked pointer, the object can be leaked.
NOTE:
一、重点是理解why "If the pointer returned by
new
is assigned to a plain/naked pointer, the object can be leaked"?在下面的文章中,给出了回答:a、sean-parent Value Semantics and Concept-based Polymorphism
document.emplace_back(new my_class_t());
defects:
1、An instance of
my_class_t
will be allocated first2、Then the document will grow to make room
3、If growing the document throws an exception, the memory from
my_class_t
is leaked二、使用RAII、smart pointer object generator
下面的"Enforcement"也是符合这个观点的
Note
In a large program, a naked delete
(that is a delete
in application code, rather than part of code devoted to resource management) is a likely bug: if you have N delete
s, how can you be certain that you don't need N+1 or N-1? The bug might be latent(隐藏的): it might emerge only during maintenance. If you have a naked new
, you probably need a naked delete
somewhere, so you probably have a bug.
NOTE:
1、这一节其实是在说明calling new and delete explicitly的缺点
Enforcement
(Simple) Warn on any explicit use of new
and delete
. Suggest using make_unique
instead.
R.12: Immediately give the result of an explicit resource allocation to a manager object
Reason
If you don't, an exception or a return might lead to a leak.
Example, bad
void f(const string& name)
{
FILE* f = fopen(name, "r"); // open the file
vector<char> buf(1024);
auto _ = finally([f] { fclose(f); }); // remember to close the file
// ...
}
The allocation of buf
might fail and leak the file handle.
NOTE:
1、上述code是典型的scope guard
Example
void f(const string& name)
{
ifstream f{name}; // open the file
vector<char> buf(1024);
// ...
}
The use of the file handle (in ifstream
) is simple, efficient, and safe.
R.13: Perform at most one explicit resource allocation in a single expression statement
R.14: Avoid []
parameters, prefer span
Reason
An array decays to a pointer, thereby losing its size, opening the opportunity for range errors. Use span
to preserve size information.
NOTE:
1、关于span,参见
C++\Library\Standard-library\STL\Containers-library\View\span
章节
Example
void f(int[]); // not recommended
void f(int*); // not recommended for multiple objects
// (a pointer should point to a single object, do not subscript)
void f(gsl::span<int>); // good, recommended
Enforcement
Flag []
parameters. Use span
instead.
TODO
1、modernescpp C++ Core Guidelines: Rules to Resource Management