第1章:C++ 必知必会

对齐原书第 1 章:RAII、pimpl、C++11/14/17 初始化与注解、final/override/default/delete、auto/range-for/structured binding、STL emplace 与智能指针。

学习目标

  • 能实现 exception-safe 的 RAII resource wrapper 与 pimpl boundary,证明 acquire、move、release、destructor 与 incomplete type 的 ownership contract
  • 能比较 initializer_list、attributes、final/override/default/delete、auto、range-for、structured binding 和 emplace APIs,判断 copy、overload、lifetime 与 diagnostic 差异
  • 能设计 unique/shared/weak ownership graph,正确使用 make functions 与 enable_shared_from_this,并诊断 double delete、strong cycle 和 control-block 错误

机制总览

服务器对象的 owner、接口与销毁顺序

  1. 1

    建立 owner

    RAII 把资源取得和对象生命周期绑定,unique_ptr 表达唯一所有权。

  2. 2

    暴露接口

    pimpl 隔离 layout 和重依赖,override/delete 让意图可被编译器检查。

  3. 3

    共享与回调

    shared_ptr 只用于真实共享,weak_ptr 打断非所有权回边。

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

服务器对象的 owner、接口与销毁顺序

沿对象生命周期检查 RAII、pimpl、智能指针和现代 C++ 语法是否表达同一份责任。

选择推理阶段

当前阶段 · 建立 owner

RAII 把资源取得和对象生命周期绑定,unique_ptr 表达唯一所有权。

可核验证据

构造/析构计数、sanitizer 与 owner graph。

服务器 C++ 的首要问题不是语法新旧,而是资源责任能否从构造、共享、回调一直证明到析构。

失效—证据矩阵

服务器对象的 owner、接口与销毁顺序

建立 owner

典型失效

裸 new/delete 分散在多个退出路径,异常时泄漏。

核验证据

构造/析构计数、sanitizer 与 owner graph。

暴露接口

典型失效

头文件泄漏实现细节,错误重载静默创建新成员。

核验证据

ABI diff、依赖构建时间与编译诊断。

共享与回调

典型失效

循环引用或从 this 临时创建第二个 control block。

核验证据

use_count 只作诊断、heap path 与析构断言。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么服务器开发先从 C++ resource contract 开始

服务器长期持有 socket、buffer、file、thread、mutex guard 和 callback。connect 失败、allocation 抛异常、early return、peer close 与 shutdown 竞态让错误路径远多于正常路径。若 cleanup 依赖“每个分支都记得 close”,低概率遗漏最终会积成 fd、memory 或 thread leak。

原书第 1 章先建立共同底座:RAII 把 resource 与 lifetime 绑定;pimpl 隔离 implementation;C++11/14/17 把 intent 交给 compiler;smart pointers 把 heap ownership graph 显式化。

目录中的 C++ 11/14/17 新增的实用特性不是语法新闻,而是贯穿 initializer、attributes、override/default/delete、deduction、iteration 与 container construction 的可审核 contract。

1.1 RAII:让所有退出路径共用 destructor

原书从手工 cleanup、goto、do-while-zero 演进到 RAII。集中 label 比复制释放语句少漏分支,但仍靠人工维护 acquire/release 顺序;RAII 才把顺序放进 object lifetime。

class Socket final {
 public:
  explicit Socket(int fd = -1) noexcept : fd_(fd) {}
  ~Socket() { if (fd_ >= 0) ::close(fd_); }
 
  Socket(const Socket&) = delete;
  Socket& operator=(const Socket&) = delete;
 
  Socket(Socket&& other) noexcept
      : fd_(std::exchange(other.fd_, -1)) {}
 
  Socket& operator=(Socket&& other) noexcept {
    if (this != &other) {
      if (fd_ >= 0) ::close(fd_);
      fd_ = std::exchange(other.fd_, -1);
    }
    return *this;
  }
 private:
  int fd_;
};

normal return、early return 和 都走同一个 destructor。析构函数通常必须 noexcept;若析构在另一个 active exception 期间抛出,程序会 terminate。

1.2 pimpl:隐藏 layout 与重依赖

// session.h
class Session {
 public:
  Session();
  ~Session();
  Session(Session&&) noexcept;
  Session& operator=(Session&&) noexcept;
 private:
  class Impl;
  std::unique_ptr<Impl> impl_;
};
// session.cpp
class Session::Impl { Buffer input; Parser parser; };
Session::Session() : impl_(std::make_unique<Impl>()) {}
Session::~Session() = default;
Session::Session(Session&&) noexcept = default;
Session& Session::operator=(Session&&) noexcept = default;

unique_ptr 可声明 incomplete pointee,但删除 Impl 需要 complete type,所以 destructor 在 .cpp 定义。pimpl 可减少 rebuild fan-out、隐藏 layout 并辅助 ,代价是 allocation、indirection 和 special-member 设计;小 value type 不必机械使用。

1.3 C++11/14/17 特性是 contract 工具

统一初始化与 initializer_list

struct Config {
  std::string host{"127.0.0.1"};
  std::uint16_t port{8080};
  std::size_t workers{4};
};

braces 可拒绝 narrowing,但会优先考虑 vector<int>(10, 2) 是十个 2,vector<int>{10, 2} 是两个 elements;统一语法不等于统一语义。

attributes、final、override、default、delete

[[nodiscard]] 提醒 caller 不应丢掉 status/handle;[[fallthrough]] 声明 switch fallthrough 是 intentional。诊断强度由 implementation 决定,attribute 不是 runtime validation。

class Handler { public: virtual void on_data(std::string_view) = 0; };
class Login final : public Handler {
 public: void on_data(std::string_view) override;
};

不写 override 时,const/reference/noexcept mismatch 可能静默形成新 overload。=default=delete,可拒绝 copy 或危险 implicit conversion。

auto、range-for 与 structured binding

auto value = expr 常产生 copy,auto& alias lvalue,const auto& 只读借用。是否写 & 比是否写 auto 更影响 ownership 和性能。

for (auto& connection : connections) connection.poll();

自定义 type 提供 member begin/end 或 ADL free functions 即可支持 Range-based 循环。临时 range 的 lifetime 和 proxy iterator 仍需审查。

for (auto& [fd, session] : sessions) session.on_ready(fd);

若省略 &,map pair/value 可能被复制,修改 session 不影响 container。

emplace、try_emplace 与 insert_or_assign

sessions.try_emplace(fd, fd, peer_address);

insert_or_assign 则明确“缺失就 insert,存在就 assign”。emplace 不保证永远零拷贝或更快,conversion、allocator、exception 与 duplicate-key policy 仍需测量。

1.4 智能指针:先画 owner graph

auto_ptr 与 unique_ptr

unique_ptr 用 deleted copy + explicit move 取代 auto_ptr。

auto session = std::make_unique<Session>(fd);
workers.push_back(std::move(session));

release 只在立即交给另一个明确 owner 时使用;get 只借出 raw pointer;reset 会释放旧 object。默认先 unique ownership,确有共享 lifetime 再 shared_ptr。

shared_ptr 与 control block

make_shared 通常一次 allocation 创建 object/control block。。同一 raw pointer 分别构造两个 shared_ptr 会形成两个 control blocks 并 double delete。

shared_ptr 的 control block 操作可并发,不代表 pointee fields thread-safe;多个 threads 修改 Session 仍需 mutex 或 single-owner-thread discipline。use_count()==1 也不是同步原语。

enable_shared_from_this 与 weak_ptr

class Session : public std::enable_shared_from_this<Session> {
 public:
  void start() {
    reactor.register_callback([weak = weak_from_this()] {
      if (auto self = weak.lock()) self->on_ready();
    });
  }
};

object 必须先由 shared_ptr 管理;对 stack/unique object 或 constructor 中过早 shared_from_this 会失败。绝不能 shared_ptr<Session>(this),那会创建第二 control block。

expired 只是瞬时查询;真正访问应直接 lock 并在 returned shared_ptr lifetime 内完成。parent-child、subject-observer 与 callbacks 用 weak 表达 non-owning reverse edge,打破

unique_ptr 默认 deleter 常为一个 pointer 大小;stateful deleter 可增大 object。shared_ptr 通常含 object/control-block 两个 pointers,具体 ABI 应对当前 implementation 用 sizeof/benchmark 验证。

三步验收第 1 章

分步1 / 3

第一步:在不同 acquire 阶段失败

先预测 manual、goto 与 RAII 各释放哪些资源,再移动 failure point。验收 every early return/exception path,而不是只跑 normal path。

小结

  • RAII 合并 acquire/release 与 lifetime;copy/move/empty state/noexcept destructor 仍需完整设计
  • pimpl 用 incomplete Impl + unique_ptr 建 compilation firewall,并承担 allocation/indirection 代价
  • initializer_list 可改变 braces overload;final/override/default/delete 与 attributes 把 intent 交给 compiler
  • auto、range-for、structured binding 的 qualifier 决定 copy、alias 与 lifetime
  • emplace、try_emplace、insert_or_assign 表达不同 construction 与 duplicate-key policy
  • auto_ptr 被 move-only unique_ptr 取代;shared_ptr 共享 control block,weak_ptr 表达 non-owning edge
  • enable_shared_from_this 只能在 control block 已建立后调用;shared_ptr 不同步 pointee,也不自动打破 cycle

练习

  1. 问题 1:审查 Socket wrapper。 destructor close(fd),却保留默认 copy;move 又不清空 source。会发生什么,怎样修复?
  1. 问题 2:解释 auto itemauto [k,v]vector<int>{10,2} 三段风险。
  1. 问题 3:Session 用 shared_ptr(this) 注册 callback,Reactor 又被 Session 拥有。指出两个 ownership bug 并修复。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

resource ownership contract
resource acquire、move/copy、release 与 failure cleanup 约定。
RAII idiom
constructor 建 ownership、destructor 随 scope exit release。
unique resource owner
copy 禁止、move 转移、同一 resource 只有一个 owner。
stack unwinding
exception 传播时 automatic objects 逆序析构。
cleanup label
goto 汇聚 error paths 并逆序释放的结构。
pimpl idiom
public object 通过 pointer 持有私有 Impl。
compilation firewall
forward-declared Impl 隔离 implementation includes/layout。
ABI stability
layout/symbol/calling convention 对 binary consumer 保持兼容。
uniform initialization
braces 统一表示多类初始化的语法。
initializer_list overload
braced list 优先考虑的 element-view constructor。
standard attribute
双中括号标准 metadata/diagnostic annotation。
override contract
要求 member 匹配 base virtual 的检查。
final contract
禁止进一步 inheritance/override 的声明。
defaulted function
compiler 生成默认 special-member definition。
deleted function
overload 命中即不可调用的 function。
auto deduction
从 initializer 推导 variable type。
range-based for
通过 begin/end protocol 遍历 range。
structured binding
把 tuple-like/aggregate components 引入多个 names。
in-place construction
container 用 arguments 直接构造 element。
try_emplace semantics
key 缺失才构造 mapped value。
auto_ptr legacy
copy 转移 owner、已从 C++17 移除的旧 pointer。
unique_ptr ownership
move-only exclusive pointer/deleter ownership。
shared_ptr control block
保存 strong/weak counts 与 deleter 的 metadata。
strong reference count
归零时销毁 shared object 的 owner count。
enable_shared_from_this contract
从已托管 object 取得同一 control block owner。
weak_ptr observer
不增加 strong count、用 lock 临时取得 owner。
strong ownership cycle
strong edges 成环导致 count 不归零的 leak。

资料与写作方式声明

本章以C++服务器开发精髓,第1章 C++必知必会权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…