CppCon筆記--Back to Basics: RAII and the Rule of Zero
1.RAII 和 rule of three
C++程式設計很多時候需要手動管理資源,其中包括資源的獲取,使用和釋放,而手動對資源釋放是很容易出錯的一個環節。
根據C++的特性,當局部物件的生命週期結束時,會呼叫解構函式,因此藉由類的解構函式對資源進行釋放就是RAII的工作原理。
但是這段程式碼仍然存在問題,如果對vector進行復制,此時的析構會進行double release,程式碼報錯。這裡就引入了C++的第一個rule of thumb。
當類直接對一些資源進行管理時,你需要手寫三個成員函式:
- 解構函式,釋放資源
- 拷貝函式,選擇正確的拷貝方式,是否深拷貝,是否釋放rhs的物件
- 拷貝建構函式,同樣是選擇正確的拷貝方式
不要忘了有時候可以用swap-and-copy來處理,會更加安全。
比如這個例子self-assign會記憶體洩漏
這個例子中能self-assign,但是用成員資料賦值會出問題。
2.RAII 和異常安全
利用RAII的特性,對資源進行封裝。
nocopyable -> delete 拷貝賦值函式和拷貝建構函式
default -> 編譯器會自動生成拷貝或移動的建構函式
3.Rule of zero
如果你的類沒有直接管理任何資源,僅僅使用標準庫,你不應該手寫任何特殊成員函式(構造器、拷貝賦值函式),應該把他們全部default了。
4.Rule of five
e of three
C++程式設計很多時候需要手動管理資源,其中包括資源的獲取,使用和釋放,而手動對資源釋放是很容易出錯的一個環節。
根據C++的特性,當局部物件的生命週期結束時,會呼叫解構函式,因此藉由類的解構函式對資源進行釋放就是RAII的工作原理。
但是這段程式碼仍然存在問題,如果對vector進行復制,此時的析構會進行double release,程式碼報錯。這裡就引入了C++的第一個rule of thumb。
當類直接對一些資源進行管理時,你需要手寫三個成員函式:
- 解構函式,釋放資源
- 拷貝函式,選擇正確的拷貝方式,是否深拷貝,是否釋放rhs的物件
- 拷貝建構函式,同樣是選擇正確的拷貝方式
不要忘了有時候可以用swap-and-copy來處理,會更加安全。
noexcept!
比如這個例子self-assign會記憶體洩漏
這個例子中能self-assign,但是用成員資料賦值會出問題。
2.RAII 和異常安全
利用RAII的特性,對資源進行封裝。
nocopyable -> delete 拷貝賦值函式和拷貝建構函式
default -> 編譯器會自動生成拷貝或移動的建構函式
3.Rule of zero
如果你的類沒有直接管理任何資源,僅僅使用標準庫,你不應該手寫任何特殊成員函式(構造器、拷貝賦值函式),應該把他們全部default了。
4.Rule of five
由於Cpp11引入了右值的概念,現在的rule of three已經有些滿足不了實際的使用了,所以引入了rule of five.
如果你的類直接對資源進行管理,你需要手寫5個特殊成員函式:
- 解構函式
- 拷貝建構函式
- 移動建構函式
- 拷貝賦值函式
- 移動賦值函式
不過拷貝和移動建構函式可以被寫成同一個函式。(題外話,印象中有一期clean code講過,有的情況這樣會引起拷貝而不是移動,還是需要注意)
Foo& operator(Foo rhs/*通過值傳遞,左右值都能構建引數,並於this交換*/)
{
swap(*this, rhs);
return *this;
}
5.回來naive vector
Arthur還起了個rule of 4.5的名字233
5.0.友元函式
友元函式可以訪問這個類的所有成員變數。這裡其實不是友元成員函式(申明某類的某函式能訪問其的所有成員),而是在類裡面定義了一個友元函式。 https://en.cppreference.com/w/cpp/language/friend
- Designates a function or several functions as friends of this class
lass Y {
int data; // private member
// the non-member function operator<< will have access to Y's private members
friend std::ostream& operator<<(std::ostream& out, const Y& o);
friend char* X::foo(int); // members of other classes can be friends too
friend X::X(char), X::~X(); // constructors and destructors can be friends
};
// friend declaration does not declare a member function
// this operator<< still needs to be defined, as a non-member
std::ostream& operator<<(std::ostream& out, const Y& y)
{
return out << y.data; // can access private member Y::data
}
- (only allowed in non-local class definitions) Defines a non-member function, and makes it a friend of this class at the same time. Such non-member function is always inline.
class X {
int a;
friend void friend_set(X& p, int i) {
p.a = i; // this is a non-member function
}
public:
void member_set(int i) {
a = i; // this is a member function
}
};
5.1.std::exchange
把第一個引數賦值到lhs,把第二個引數作為第一個引數的新值
template<class T, class U = T>
constexpr // since C++20
T exchange(T& obj, U&& new_value)
{
T old_value = std::move(obj);
obj = std::forward<U>(new_value);
return old_value;
}
e.g.
struct S
{
int n;
S(S&& other) noexcept : n{std::exchange(other.n, 0)} {}
S& operator=(S&& other) noexcept
{
if(this != &other)
n = std::exchange(other.n, 0); // move n, while leaving zero in other.n
return *this;
}
};
利用unique_ptr來簡化rule of five
N.B. 這些rule都只和資源管理有關,所以類的實現同樣可以在rule of zero上加上其他的建構函式,比如initialize list。