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 -> 编译器会自动生成拷贝或移动的构造函数

https://stackoverflow.com/questions/6502828/what-does-default-mean-after-a-class-function-declaration

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 -> 编译器会自动生成拷贝或移动的构造函数

https://stackoverflow.com/questions/6502828/what-does-default-mean-after-a-class-function-declaration

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

  1. 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
}
  1. (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。

6.一些资源管理的例子

猜你喜欢

转载自www.cnblogs.com/linsinan1995/p/13375386.html