C++ 接口应在类型和签名中编码所有权、生命周期和可替代性。C++ Core Guidelines 的 Rule of Zero 正是这一设计的结果:由 RAII 成员组成的领域值不应手动重复它们的复制、 移动和销毁行为。
资源所有者和领域值 #
资源所有者类型获取一个资源,在析构函数中释放它,并说明是否允许复制或转移。句柄表示 对资源的访问;所有者句柄还承担释放资源的责任。当标准所有者不能表达资源的获取、释放 或错误策略时,使用所有者句柄是合适的:
#include <cstdio>
#include <utility>
class File {
public:
explicit File(std::FILE* owned_handle) noexcept : handle_(owned_handle) {}
~File() { if (handle_) std::fclose(handle_); }
File(const File&) = delete;
File& operator=(const File&) = delete;
File(File&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}
File& operator=(File&& other) noexcept {
if (this != &other) {
if (handle_) std::fclose(handle_);
handle_ = std::exchange(other.handle_, nullptr);
}
return *this;
}
private:
std::FILE* handle_;
};栈展开期间析构函数不能抛出异常。当调用者必须观察释放失败时,提供显式的类似
close() 的操作,然后让析构函数执行不抛出异常的清理。确保自定义移动操作转移资源,
并使源对象处于可安全销毁的状态;默认的指针移动会复制裸指针,因此对于所有者裸句柄是
错误的。如果能够表达所有权,应优先使用带自定义删除器的 std::unique_ptr。
领域值应组合正确的所有者:
#include <string>
#include <utility>
#include <vector>
class Entry {
public:
explicit Entry(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
std::vector<std::string> labels_;
};由于 std::string 和 std::vector 已经提供值语义,Entry 不需要自定义析构函数、
复制操作或移动操作。
用继承表示接口 #
公有继承表示接口层面的“是一个”关系。只有当基类的每个有效操作对派生类型仍然有效时, 才使用公有继承;若只是为了复用实现而不需要可替代性,则使用组合。
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0;
};当客户端可以通过基类指针销毁派生对象时,多态基类需要虚析构函数。使用 override 标记
有意的重写;只有在有意关闭扩展边界时才使用 final。保持表示私有,因为允许访问可变
表示会像公开数据一样直接削弱不变量。
将参数作为所有权契约 #
在考虑复制成本之前,先根据所有权和生命周期要求选择参数形式:
| 参数 | 契约 |
|---|---|
T | 被调用方接收自己的值;适合廉价值,或被调用方会保留副本的情况。 |
const T& | 非所有者只读借用;调用者在调用期间负责维持生命周期。 |
T& | 非空可变借用;被调用方可以修改被引用对象,但不拥有它。 |
T* | 可以为空的借用;空值必须具有文档化的含义。 |
std::unique_ptr<T> 按值传递 | 所有权转移给被调用方。 |
std::shared_ptr<T> 按值传递 | 建立或延长共享所有权;仅在确实需要共享生命周期时使用。 |
按值接收 std::string 并将其移动到存储中,可以为左值和右值调用者高效表达独立保留的值:
#include <string>
#include <utility>
class User {
public:
explicit User(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
};不要使用裸指针隐藏所有权转移。当调用者承担责任时,应返回
std::unique_ptr<T> 等所有者值;只有在 API 能够说明并维持被借用对象的生命周期时,才
返回引用或非所有者指针。
评论
在 GitHub 上阅读或评论(发表评论需要登录)