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。