一句话核心结论 :C++ 高级技术的”7 大工程模式”——虚拟构造 (virtual constructor 模式)、对象计数与限制 (printer / pool 模式)、heap 限制 (new 探测 / 析构限制)、智能指针 (auto_ptr 的前世今生 + 现代 unique_ptr)、引用计数 (shared_ptr 的简化版本)、proxy class (区分 [] 的左值/右值)、双重分派 (visitor 模式)。
系列导航 前言:C++ 高级技术的”工具箱” 前面的章节讲了正确性 、性能 ——本篇讲设计 。
graph TB
A["C++ 高级技术"] --> B["虚拟构造\n(条款 25)"]
A --> C["对象计数\n(条款 26)"]
A --> D["heap 限制\n(条款 27)"]
A --> E["智能指针\n(条款 28)"]
A --> F["引用计数\n(条款 29)"]
A --> G["proxy class\n(条款 30)"]
A --> H["双重分派\n(条款 31)"]
B -.->|virtual ctor| B1["clone() 模式"]
C -.->|printer/pool| C1["对象计数 + 限制"]
D -.->|new 探测| D1["只能 stack / 只能 heap"]
E -.->|auto_ptr| E1["现代 unique_ptr"]
F -.->|ref count| F1["shared_ptr 简化"]
G -.->|proxy| G1["[] 区分 lvalue/rvalue"]
H -.->|visitor| H1["双重分派"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#B5EAD7,stroke:#80CBC4,color:#333
style F fill:#FFF9C4,stroke:#F9A825,color:#333
style G fill:#C7CEEA,stroke:#9FA8DA,color:#333
style H fill:#E8D5F5,stroke:#CE93D8,color:#333
style B1 fill:#FFF9C4,stroke:#F9A825,color:#333
style C1 fill:#FFF9C4,stroke:#F9A825,color:#333
style D1 fill:#FFF9C4,stroke:#F9A825,color:#333
style E1 fill:#FFF9C4,stroke:#F9A825,color:#333
style F1 fill:#FFF9C4,stroke:#F9A825,color:#333
style G1 fill:#FFF9C4,stroke:#F9A825,color:#333
style H1 fill:#FFF9C4,stroke:#F9A825,color:#333一、条款 25:将 constructor 和 non-member functions 虚化 1.1 为什么构造函数不能是 virtual? 1 2 3 4 5 class Widget {public : virtual Widget () ; };
原因 :
构造函数的工作是创建对象 ——vptr 还没初始化 派生类的构造函数调用前,基类的构造函数必须先完成 1.2 解决方案:Virtual Constructor Pattern 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Base {public : virtual ~Base () = default ; virtual Base* clone () const = 0 ; }; class Derived : public Base {public : Base* clone () const override { return new Derived (*this ); } }; void process (Base* pb) { Base* copy = pb->clone (); delete copy; }
1.3 实战:文档处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Document {public : virtual ~Document () = default ; virtual Document* clone () const = 0 ; virtual void open (const std::string& path) = 0 ; }; class PdfDocument : public Document {public : Document* clone () const override { return new PdfDocument (*this ); } void open (const std::string& path) override { std::cout << "Open PDF: " << path << "\n" ; } }; class WordDocument : public Document {public : Document* clone () const override { return new WordDocument (*this ); } void open (const std::string& path) override { std::cout << "Open Word: " << path << "\n" ; } }; std::unique_ptr<Document> openDocument (const std::string& path) { std::unique_ptr<Document> doc = makeDocument (path); doc->open (path); return doc; }
1.4 虚拟非成员函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Base {public : virtual ~Base () = default ; virtual std::ostream& print (std::ostream& os) const = 0 ; friend std::ostream& operator <<(std::ostream& os, const Base& b) { return b.print (os); } }; class Derived : public Base {public : std::ostream& print (std::ostream& os) const override { return os << "Derived" ; } }; Derived d; std::cout << d;
1.5 关键启示 构造函数不能 virtual ——但可以”虚拟构造”(clone())clone() 模式 ——返回基类指针,指向”自己”的拷贝非成员虚拟 ——friend operator<< + virtual print常见场景 :工厂、拷贝、序列化二、条款 26:限制某个 class 所能产生的对象数量 2.1 问题:什么时候限制对象数量? 1 2 3 4 5 6 7 8 9 10 11 12 class Printer {public : static Printer& getInstance () ; }; class ConnectionPool { static constexpr int MAX = 10 ; public : static Connection* acquire () ; };
2.2 解决方案 1:构造函数私有 + 计数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Printer { static int count_; Printer (); public : static Printer& getInstance () { static Printer instance; return instance; } }; int Printer::count_ = 0 ;Printer::Printer () { if (++count_ > 1 ) { throw std::runtime_error ("only 1 Printer" ); } }
2.3 解决方案 2:对象计数(用于统计) 1 2 3 4 5 6 7 8 9 10 11 12 13 class Widget { static int count_; public : Widget () { ++count_; } ~Widget () { --count_; } static int count () { return count_; } }; int Widget::count_ = 0 ;Widget w1, w2; std::cout << Widget::count ();
2.4 解决方案 3:限制数量的对象池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ConnectionPool { static constexpr int MAX = 10 ; Connection* pool_[MAX]; bool used_[MAX] = {false }; public : Connection* acquire () { for (int i = 0 ; i < MAX; ++i) { if (!used_[i]) { used_[i] = true ; return pool_[i]; } } return nullptr ; } void release (Connection* c) { for (int i = 0 ; i < MAX; ++i) { if (pool_[i] == c) { used_[i] = false ; return ; } } } };
2.5 关键启示 限制数量 = 单例 / 池 ——getInstance + 私有构造对象计数 = 静态成员 ——构造 +1,析构 -1应用场景 :打印机、连接池、数据库连接、单例三、条款 27:要求(或禁止)对象产生于 heap 中 3.1 场景 1:要求对象在 heap 中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class BigObject { public : BigObject () = default ; protected : ~BigObject () = default ; }; auto p = std::make_unique <BigObject>();
原理 :
栈对象在作用域结束时调析构——析构不可见 析构 protected 后,栈对象无法调(编译器拒绝) 只能 new + 智能指针 3.2 场景 2:禁止对象在 heap 中 1 2 3 4 5 6 7 8 9 10 11 12 class StackOnly {public : StackOnly () = default ; private : static void * operator new (std::size_t ) ; static void operator delete (void *) ; }; StackOnly s;
3.3 实战:组合 1 2 3 4 5 6 7 8 9 10 11 12 class NonCopyableHeap { NonCopyableHeap () = default ; NonCopyableHeap (const NonCopyableHeap&) = delete ; NonCopyableHeap& operator =(const NonCopyableHeap&) = delete ; protected : ~NonCopyableHeap () = default ; public : static std::unique_ptr<NonCopyableHeap> create () { return std::make_unique <NonCopyableHeap>(); } };
3.4 关键启示 必须在 heap ——析构 protected + 智能指针禁止在 heap ——operator new/delete 私有组合 :单例 + 不可拷贝 + 必须 heap替代方案 :工厂函数 + unique_ptr四、条款 28:智能指针(Smart Pointers) 4.1 auto_ptr:C++98 的”怪胎” 1 2 3 4 5 std::auto_ptr<Widget> p1 (new Widget()) ;std::auto_ptr<Widget> p2 = p1; *p2 = "hello" ;
问题 :
拷贝 = 转移所有权(违反直觉) 不能用于 STL 容器(vector<auto_ptr<T>> 排序破坏) C++11 起被 = delete 替代 4.2 unique_ptr:C++11 的”独占”指针 1 2 3 4 5 std::unique_ptr<Widget> p1 = std::make_unique <Widget>(); auto p2 = std::move (p1);
优势 :
4.3 shared_ptr:C++11 的”共享”指针 1 2 3 4 5 6 std::shared_ptr<Widget> p1 = std::make_shared <Widget>(); { auto p2 = p1; }
4.4 weak_ptr:打破循环引用 1 2 3 4 struct Node { std::shared_ptr<Node> next; std::weak_ptr<Node> prev; };
4.5 智能指针的”前世今生”对照 More Effective C++ 现代 C++ auto_ptr(条款 28)unique_ptr / shared_ptr自己实现引用计数(条款 29) std::shared_ptrauto_ptr 的”陷阱”unique_ptr 编译期拒绝拷贝
4.6 实战:迁移 auto_ptr → unique_ptr 1 2 3 4 5 6 7 std::auto_ptr<Widget> p (new Widget()) ;p->doSomething (); std::unique_ptr<Widget> p = std::make_unique <Widget>(); p->doSomething ();
4.7 关键启示 auto_ptr 已废弃 ——别用unique_ptr 默认首选 ——零开销共享所有权?shared_ptr ——小心循环引用weak_ptr 打破循环 ——观察者五、条款 29:Reference counting 5.1 什么是引用计数? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class String { struct StringData { char * data_; size_t refCount_; }; StringData* data_; public : String (const char * s) { data_ = new StringData; data_->data_ = strdup (s); data_->refCount_ = 1 ; } String (const String& other) : data_ (other.data_) { ++data_->refCount_; } ~String () { if (--data_->refCount_ == 0 ) { free (data_->data_); delete data_; } } };
5.2 写时复制(Copy-on-Write) 1 2 3 4 5 6 7 8 9 10 11 12 13 void String::modify (const char * newData) { if (data_->refCount_ > 1 ) { StringData* newData = new StringData; newData->data_ = strdup (data_->data_); newData->refCount_ = 1 ; --data_->refCount_; data_ = newData; } strcpy (data_->data_, newData); }
5.3 现代 C++:std::shared_ptr 1 2 3 4 5 std::shared_ptr<Widget> p1 = std::make_shared <Widget>(); auto p2 = p1;
5.4 引用计数的”4 大问题” 问题 说明 循环引用 shared_ptr 互相引用——永远不释放线程安全 计数加减需原子操作 destructor 慢 引用计数操作 内存不释放 一个大对象,多个引用
5.5 关键启示 引用计数 = 共享所有权 ——shared_ptr 的本质写时复制(COW) ——节省内存C++11 用 std::shared_ptr ——不用手写避免循环引用 ——用 weak_ptr六、条款 30:Proxy classes 6.1 什么是 proxy class? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Matrix { double data_[N][N]; public : double & operator [](int i, int j) { return data_[i][j]; } }; class Matrix {public : class Element { Matrix& m_; int i_, j_; public : Element (Matrix& m, int i, int j) : m_ (m), i_ (i), j_ (j) {} operator double () const { return m_.data_[i_][j_]; } Element& operator =(double v) { m_.data_[i_][j_] = v; return *this ; } }; Element operator [](int i, int j) { return Element (*this , i, j); } }; Matrix m; m[0 ][0 ] = 3.14 ; double x = m[0 ][0 ];
6.2 经典应用:智能引用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class String { std::string s_; public : class CharProxy { String& s_; size_t pos_; public : CharProxy (String& s, size_t pos) : s_ (s), pos_ (pos) {} operator char () const { return s_.s_[pos_]; } CharProxy& operator =(char c) { s_.s_[pos_] = c; return *this ; } }; CharProxy operator [](size_t i) { return CharProxy (*this , i); } };
6.3 现代 C++:std::reference_wrapper + std::span 1 2 3 4 5 std::reference_wrapper<int > r = std::ref (x); void process (std::span<int > data) ;
6.4 关键启示 proxy = 代理类型 ——“假装”是另一种类型区分 operator[] 的左值/右值 ——主要应用替代方案 :std::reference_wrapper / std::spanC++20 std::span ——更现代七、条款 31:让函数根据一个以上的对象类型来决定 7.1 什么是双重分派(Double Dispatch)? 1 2 3 4 5 6 7 8 9 10 class Shape { };class Circle : public Shape { };class Square : public Shape { };void process (const Shape& s1, const Shape& s2) { }
7.2 经典反例:虚函数只支持单分派 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Shape {public : virtual bool intersect (const Shape& other) const = 0 ; }; class Circle : public Shape {public : bool intersect (const Shape& other) const override { if (auto * c = dynamic_cast <const Circle*>(&other)) { return circleCircle (c); } if (auto * s = dynamic_cast <const Square*>(&other)) { return circleSquare (s); } return false ; } };
问题 :
dynamic_cast 慢不是”真正”的双重分派 类型扩展性差 7.3 解决方案:Visitor 模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Circle ;class Square ;class Visitor {public : virtual void visit (Circle& c) = 0 ; virtual void visit (Square& s) = 0 ; }; class Shape {public : virtual ~Shape () = default ; virtual void accept (Visitor& v) = 0 ; }; class Circle : public Shape {public : void accept (Visitor& v) override { v.visit (*this ); } }; class Square : public Shape {public : void accept (Visitor& v) override { v.visit (*this ); } }; class CollisionVisitor : public Visitor {public : void visit (Circle& c) override { std::cout << "Circle\n" ; } void visit (Square& s) override { std::cout << "Square\n" ; } }; void detectCollision (Shape& s1, Shape& s2) { CollisionVisitor v; s1. accept (v); s2. accept (v); }
7.4 现代 C++:std::variant + std::visit 1 2 3 4 5 6 7 8 9 10 11 12 using Shape = std::variant<Circle, Square>;class CollisionVisitor {public : void operator () (Circle& c) { std::cout << "Circle\n" ; } void operator () (Square& s) { std::cout << "Square\n" ; } }; void detectCollision (Shape& s1, Shape& s2) { std::visit (CollisionVisitor{}, s1, s2); }
7.5 关键启示 单分派 = 1 个 virtual(f(x) 中 x 决定)双重分派 = 2 个 virtual(f(x, y) 中 x, y 决定)经典实现 = Visitor 模式C++17 现代实现 = std::variant + std::visit八、7 个条款的”技术”全景 graph TB
A["C++ 高级技术"] --> B["虚拟构造\n(条款 25)"]
A --> C["对象计数\n(条款 26)"]
A --> D["heap 限制\n(条款 27)"]
A --> E["智能指针\n(条款 28)"]
A --> F["引用计数\n(条款 29)"]
A --> G["proxy class\n(条款 30)"]
A --> H["双重分派\n(条款 31)"]
B --> B1["clone() 模式"]
C --> C1["printer / pool"]
D --> D1["operator new 私有"]
E --> E1["unique_ptr 替代 auto_ptr"]
F --> F1["shared_ptr 简化"]
G --> G1["[] 区分 lvalue/rvalue"]
H --> H1["visitor 模式"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#B5EAD7,stroke:#80CBC4,color:#333
style F fill:#FFF9C4,stroke:#F9A825,color:#333
style G fill:#C7CEEA,stroke:#9FA8DA,color:#333
style H fill:#E8D5F5,stroke:#CE93D8,color:#333
style B1 fill:#FFF9C4,stroke:#F9A825,color:#333
style C1 fill:#FFF9C4,stroke:#F9A825,color:#333
style D1 fill:#FFF9C4,stroke:#F9A825,color:#333
style E1 fill:#FFF9C4,stroke:#F9A825,color:#333
style F1 fill:#FFF9C4,stroke:#F9A825,color:#333
style G1 fill:#FFF9C4,stroke:#F9A825,color:#333
style H1 fill:#FFF9C4,stroke:#F9A825,color:#333九、常见误区与陷阱 9.1 误区 1:用 auto_ptr 在容器中 1 2 3 4 5 std::vector<std::auto_ptr<Widget>> v; std::vector<std::unique_ptr<Widget>> v;
9.2 误区 2:循环引用 1 2 3 4 5 6 7 8 9 10 11 struct Node { std::shared_ptr<Node> next; std::shared_ptr<Node> prev; }; struct Node { std::shared_ptr<Node> next; std::weak_ptr<Node> prev; };
9.3 误区 3:双重分派用 dynamic_cast 1 2 3 4 5 6 7 8 9 bool intersect (const Shape& other) { if (auto * c = dynamic_cast <const Circle*>(&other)) { return circleCircle (c); } }
十、C++11/14/17 的演进 主题 C++98 时代 C++11/14/17/20 时代 智能指针 auto_ptrunique_ptr / shared_ptr / weak_ptr引用计数 自己写 std::shared_ptr双重分派 Visitor 模式 std::variant + std::visitproxy 自己写 std::reference_wrapper / std::span虚拟构造 clone() 模式std::function / 工厂单例 static + 私有构造std::call_once + static
C++17 的 std::variant :
1 2 using Shape = std::variant<Circle, Square>;std::visit (visitor, shape);
C++20 的 std::span :
1 void process (std::span<int > data) ;
十一、面试高频考点 11.1 必背题 题目 答案要点 虚拟构造怎么实现? clone() 模式auto_ptr 有什么问题?拷贝 = 转移所有权 unique_ptr vs shared_ptr?独占 vs 共享 循环引用怎么解决? weak_ptr什么是双重分派? 根据 2 个对象类型决定 Visitor 模式? 双重分派的经典实现 引用计数有什么问题? 循环引用 + 线程安全 什么是 proxy? 代理类,区分 lvalue/rvalue
11.2 高频追问 追问 关键点 clone() 模式怎么用? 返回基类指针,指向”自己”的新对象 单例怎么写? static + 私有构造 + 计数heap 限制怎么实现? 析构 protected 或 operator new 私有 写时复制(COW)? 引用计数 + 修改时深拷贝 std::variant 怎么用? 类型安全的 union + visit Visitor 模式 vs std::visit? Visitor 是面向对象;visit 是模板元编程
十二、配套实验 12.1 实验 1:clone() 模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <memory> class Shape {public : virtual ~Shape () = default ; virtual std::unique_ptr<Shape> clone () const = 0 ; virtual void draw () const = 0 ; }; class Circle : public Shape {public : std::unique_ptr<Shape> clone () const override { return std::make_unique <Circle>(*this ); } void draw () const override { std::cout << "Circle\n" ; } }; class Square : public Shape {public : std::unique_ptr<Shape> clone () const override { return std::make_unique <Square>(*this ); } void draw () const override { std::cout << "Square\n" ; } }; int main () { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back (std::make_unique <Circle>()); shapes.push_back (std::make_unique <Square>()); for (const auto & s : shapes) { auto copy = s->clone (); copy->draw (); } return 0 ; }
12.2 实验 2:智能指针迁移 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <memory> class Widget {public : Widget () { std::cout << "Widget ctor\n" ; } ~Widget () { std::cout << "Widget dtor\n" ; } void hello () { std::cout << "Hello!\n" ; } }; int main () { auto p1 = std::make_unique <Widget>(); p1->hello (); auto sp1 = std::make_shared <Widget>(); { auto sp2 = sp1; std::cout << "use_count: " << sp1. use_count () << "\n" ; } std::cout << "use_count: " << sp1. use_count () << "\n" ; return 0 ; }
12.3 实验 3:双重分派 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <variant> #include <vector> class Circle ;class Square ;class CollisionVisitor {public : void operator () (Circle& a, Circle& b) { std::cout << "Circle vs Circle\n" ; } void operator () (Circle& a, Square& b) { std::cout << "Circle vs Square\n" ; } void operator () (Square& a, Circle& b) { std::cout << "Square vs Circle\n" ; } void operator () (Square& a, Square& b) { std::cout << "Square vs Square\n" ; } }; class Circle { public : int r = 1 ; };class Square { public : int s = 2 ; };using Shape = std::variant<Circle, Square>;int main () { std::vector<Shape> shapes = {Circle{}, Square{}, Circle{}}; for (size_t i = 0 ; i < shapes.size (); ++i) { for (size_t j = 0 ; j < shapes.size (); ++j) { std::visit (CollisionVisitor{}, shapes[i], shapes[j]); } } return 0 ; }
12.4 实验 4:proxy 区分 lvalue/rvalue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <vector> class Matrix { std::vector<std::vector<double >> data_; public : Matrix (int rows, int cols) : data_ (rows, std::vector <double >(cols, 0 )) {} class Element { Matrix& m_; int i_, j_; public : Element (Matrix& m, int i, int j) : m_ (m), i_ (i), j_ (j) {} operator double () const { return m_.data_[i_][j_]; } Element& operator =(double v) { m_.data_[i_][j_] = v; return *this ; } }; Element operator [](int i, int j) { return Element (*this , i, j); } }; int main () { Matrix m (3 , 3 ) ; m[0 ][0 ] = 1.0 ; double x = m[0 ][0 ]; std::cout << "m[0][0] = " << x << "\n" ; return 0 ; }
十三、回到 7 条黄金法则 条款 黄金法则 25 虚拟构造 = clone() 模式 26 限制数量 = 单例 / 池 / 计数 27 heap 限制 = 析构 protected 或 operator new 私有 28 智能指针 = unique_ptr 替代 auto_ptr 29 引用计数 = shared_ptr 简化 30 proxy = 区分 [] 的左值/右值 31 双重分派 = Visitor 模式 / std::variant + std::visit
十四、结尾思考题 思考题 1 :实现一个 clone() 模式的多态工厂。
思考题 2 :把代码里的 auto_ptr 迁移到 unique_ptr / shared_ptr。
思考题 3 :用 std::variant + std::visit 实现一个表达式求值器(支持 int、double、string)。
思考题 4 :Visitor 模式和 std::visit 的差异是什么?各自的优劣。
思考题 5 :你的项目里有哪些”循环引用”?用 weak_ptr 改写。
十五、本篇速查表 主题 关键 API / 模式 适用场景 虚拟构造 clone() 模式多态工厂 对象限制 单例 / 池 资源限制 heap 限制 析构 protected 内存控制 unique_ptr std::make_unique默认智能指针 shared_ptr std::make_shared共享所有权 weak_ptr 观察者 打破循环 双重分派 Visitor / std::visit 碰撞检测
十六、系列导航 下一篇 :第 8 篇《杂项 + 总结:未来时态、标准库、命名空间、临时对象》——条款 32-35 一起讲透 C++ 杂项:在未来时态下发展程序、将非尾端类设计为抽象类、C++ 和 C 混合编程、让自己习惯于标准 C++ 语言。
行动建议 :
今天 :用 unique_ptr 替换你项目里的 auto_ptr今天 :识别你项目的循环引用——改用 weak_ptr本周 :用 Visitor 模式或 std::visit 优化你的双重分派本周 :用 clone() 模式设计你的多态工厂思考 :你的项目能用 std::variant 替代 union + 类型标志吗?