C++核心準則R.3: 原始指針(T*)不應擁有所有權

C++核心準則R.3: 原始指針(T*)不應擁有所有權

R.3: A raw pointer (a T*) is non-owning

R.3: 原始指針(T*)不應擁有所有權

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.

這一點不存在任何例外(無論是C++標準還是大部分代碼)而且大多數原始指針就是沒有所有權的。我們需要所有權指針被定義出來,這樣就可以可靠地,高效地刪除所有權指針指向的對象。

Example(示例)

<code>void f()
{
int* p1 = new int{7}; // bad: raw owning pointer
auto p2 = make_unique(7); // OK: the int is owned by a unique pointer
// ...
}
/<code>

The unique_ptr protects against leaks by guaranteeing the deletion of its object (even in the presence of exceptions). The T* does not.

unique_ptr通過(即使在發生異常的情況下)保證對象的刪除來防止內存洩露。而T*不會。

Example(示例)

<code>template<typename>
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
// ...
};/<typename>/<code>

We can fix that problem by making ownership explicit:

我們可以通過明確地賦予所有權來解決這個問題。

<code>template<typename>
class X2 {
public:
owner p; // OK: p is owning
T* q; // OK: q is not owning
// ...
};
/<typename>/<code>

Exception(例外)

A major class of exception is legacy code, especially code that must remain compilable as C or interface with C and C-style C++ through ABIs. The fact that there are billions of lines of code that violate this rule against owning T*s cannot be ignored. We'd love to see program transformation tools turning 20-year-old "legacy" code into shiny modern code, we encourage the development, deployment and use of such tools, we hope the guidelines will help the development of such tools, and we even contributed (and contribute) to the research and development in this area. However, it will take time: "legacy code" is generated faster than we can renovate old code, and so it will be for a few years.

例外主要來源於既有代碼,特別是那些由於ABIs必須保持C兼容性或者包含C或C風格C++接口的情況。事實上存在成百萬行的代碼違反這條反對T*持有所有權的準則,它們不能被忽略。我們很高興地看到軟件工具可以將20年以上的老代碼轉換為嶄新的現代C++代碼,我們鼓勵這類工具的開發、部署和使用,我們甚至向這個領域的研究做出了貢獻,而且將繼續做出貢獻。然而這需要時間:“既有代碼”的產生快於我們翻新舊代碼的速度,因此這個過程將會持續一些年。

This code cannot all be rewritten (even assuming good code transformation software), especially not soon. This problem cannot be solved (at scale) by transforming all owning pointers to unique_ptrs and shared_ptrs, partly because we need/use owning "raw pointers" as well as simple pointers in the implementation of our fundamental resource handles. For example, common vector implementations have one owning pointer and two non-owning pointers. Many ABIs (and essentially all interfaces to C code) use T*s, some of them owning. Some interfaces cannot be simply annotated with owner because they need to remain compilable as C (although this would be a rare good use for a macro, that expands to owner in C++ mode only).

不可能所有的代碼都被重寫(即使存在優秀的轉換軟件),很快重寫更不可能。這個問題不可能通過將所有具有所有權指針轉換為unique_ptr和shared_ptr來解決,一部分的原因是基礎的資源持有者需要/使用具有所有權的原始指針(同時也是簡單指針)。例如,普通的vector實現包含一個所有權指針和兩個非所有權指針。很多ABI(本質上講所有面向C語言的接口)使用T*,其中有些具有所有權。因為需要維持C語言可編譯,因此有些接口不能簡單地加註所有權(雖然這是很少見的宏的好用法,它只是在C++模式時展開成為所有權指針)。

Note(注意)

owner 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 is a member of a class, that class better have a destructor that deletes it.

owner沒有包含任何超過T*的語義。它不修改任何代碼就可以使用而且不會影響ABI。它只是面向程序員和分析工具的一個指示信息。例如,如果owner是某類的成員,那個最好準備一個析構函數來銷燬它。

Example, bad(反面示例)

Returning a (raw) pointer imposes a lifetime management uncertainty on the caller; that is, who deletes the pointed-to object?

返回一個(原始)指針會增加調用者生命週期管理的不確定性;即:誰應該銷燬指針指向的對象?

<code>Gadget* make_gadget(int n)
{
auto p = new Gadget{n};
// ...
return p;
}

void caller(int n)
{
auto p = make_gadget(n); // remember to delete p
// ...
delete p;
}/<code>

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):

這段代碼可以排除來自內存洩漏問題的痛苦,但是增加了虛假的分配和釋放操作和沒有必要的冗長性。如果Gadget很容易移出函數(也就是說,很小或者存在高效的移動操作),直接只用傳值(參照“輸出“返回值)。

<code>Gadget make_gadget(int n)
{
Gadget g{n};
// ...
return g;
}/<code>

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."

如果需要指針語義(例如因為返回的指針需要指向類層次中的基類(某個接口)),返回一個智能指針。

Enforcement(實施建議)

  • (Simple) Warn on delete of a raw pointer that is not an owner.
  • (簡單)警告銷燬owner類型之外的原始指針的情況。
  • (Moderate) Warn on failure to either reset or explicitly delete an owner pointer on every code path.
  • (中等)如果沒有在所有代碼路徑上重置或者銷燬onwer指針,發出警告。
  • (Simple) Warn if the return value of new is assigned to a raw pointer.
  • (簡單)如果new的返回值賦給原始指針,發出警告。
  • (Simple) Warn if a function returns an object that was allocated within the function but has a move constructor. Suggest considering returning it by value instead.、
  • (簡單)如果一個函數返回的對象在函數內分配內存,但是包含移動構造函數。提出建議通過傳值方式返回。

原文鏈接:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#r3-a-raw-pointer-a-t-is-non-owning


覺得本文有幫助?請分享給更多人。

關注【面向對象思考】輕鬆學習每一天!

面向對象開發,面向對象思考!


分享到:


相關文章: