杂项

RAII & 智能指针

RAII

RAII: Resource acquisition is initialization

将资源管理交给其本身的思想。

尽量使用对象的生命周期来管理资源,初始化时调用构造函数进行资源分配,离开作用域时自动调用析构函数释放资源。而不是自己来管理资源的分配与释放。

智能指针

所以很自然想到智能指针:

template<class T>
class auto_ptr{
	public:
		auto_ptr(T *p=0):ptr(p) {}
		~auto_ptr() { delete ptr; }
		T* operator->() const { return ptr; }
		T& operator *() const { return *ptr; }
	private:
		T* ptr;
}

这种手搓的是会出问题的。比如拷贝构造后,两个智能指针对象指向同一块内存,会导致这块内存被析构函数释放两次。

标准库智能指针

头文件:#include <memory>

auto_ptr

和手搓的基本一样。代码不是很复杂的情况下用这个就行。

unique_ptr

独占所有权的指针。就是说,unique_ptr 指向的内存只能被它一个人占有。同一时间内只会有一个 unique_ptr 指向同一块内存。

这建立在你完全使用 unique_ptr 提供的接口的情况之上。也就是说完全有办法让两个 unique_ptr 指向同一块内存。

C++14 以后,使用 std::make_unique 来创建 unique_ptr。

特点是:

  • 不允许拷贝构造
  • 支持移动语义
using int_ptr=std::unique_ptr<int>;

int *ptr = new int(5);

int_ptr p(ptr);
int_ptr p_ = std::make_unique<int>(5);  // In c++14
cout<< *p << endl;  // 5

int_ptr q = p;  // you can not clone unique_ptr!!!

int_ptr r(std::move(p));  // move semantic
cout<< *r << endl;  // 5

int_ptr s(ptr);  // this will cause double free!!!

shared_ptr

含引用计数的指针。这样就可以实现多个指针指向同一对象了。

当指向某内存的最后一个 shared_ptr 被释放以后才会释放这块内存。

特点:

  • 有复制构造函数,支持共享所有权
  • 支持移动语义
  • 避免多次释放内存
using int_ptr=std::shared_ptr<int>;

int *ptr = new int(5);

int_ptr p(ptr);
int_ptr p_ = std::make_shared<int>(5);  // In c++ 14

int_ptr q = p;  // ok :)

但还是无法解决循环引用的问题。要结合 weak_ptr 完成。

weak_ptr

专门用来解决循环引用问题的智能指针,真气派。

只能配合 shared_ptr 使用。简单来说就是一种不参与引用计数的 shared_ptr。

所以 weak_ptr 可以从从一个 shared_ptr 或另一个 weak_ptr 对象构造。并且 weak_ptr 完全不负责资源管理,没有 RAII 的特性。

青春版 goto

有时候用 goto 会使得分支结构变得简单,如下:

if(check) {
	goto bad;
}
work();
return;
bad:
	exception();

但是 goto 不让用,这时候可以用 do_while(0) 代替:

do {
	if(check) {
		break;
	}
	work();
	return;
}while(0);
exception();

容器扩容中的移动

tldr: STL 容器的元素类需要移动构造,扩容时触发。

STL 容器在扩容时的步骤:

  • 申请一块更大的新空间(不同编译器扩容倍率不同,GCC 似乎是 2 倍扩容)
  • 逐个调用旧空间中每个元素的移动构造,移动到新的空间
  • 回收旧空间

所以,STL 扩容并非字面意思上的“扩建”,而是住不下了就搬家。 带来的问题 & 需要注意的点:

  • 需要保证元素类有移动构造或者拷贝构造。最好是移动构造,更符合“搬家”的语义。
  • 迭代器、指针会失效。因为还在指向扩容前的空间。

when const

tldr: 在类中使用 const 方法表达逻辑不可变性,而不是用 const 成员变量 即用接口保证不变性,不要让成员变量阻断类型系统的优化路径。

刚学 c++ 时,有一个流传甚广的 “golden rule”:

Use const whenever possible —— Effective C++ Item 3

很容易写出 const 成员变量。这会导致对象无法移动、赋值,失去值语义。这实际上违反了现代 C++ 的核心哲学。

作为 solution,请使用接口表达逻辑不可变性:

class A{
public:
	int get_id() const { return id; }
private:
	int id;  // Better not add const here
}

reference

Herb Sutter:

❝ Do not put const on member variables unless you really want to break assignment, move, and containers. ❞
—— “Elements of Modern C++ Style”

Bjarne Stroustrup:

const is a great idea for local variables and references in functions, but it’s almost always a bad idea on class members. ❞
—— C++ Core Guidelines

Google C++ Style Guide:

Avoid const data members. They make classes harder to use with standard containers and cause surprising behavior.