3.0 建议
1 类
(1) 类: 表示 应用中的 概念
(2) 具体类: 表示 `简单概念 或 性能关键的组件`
(3) 抽象类: 接口 和 实现 需 `完全分离` 时, 用作 接口
(4) 区分 接口继承 和 实现继承
(5) 成员 v 的 dtor 被 所属类 的 dtor 隐式调用
2 资源 & RAII & 容器
(1) 管理资源: 资源句柄 & RAII
(2) 不要泄露 任何你认为是 资源的东西
3 容器
(1) 同类型值 集合 -> 存到 容器: 作 资源管理 (类)模板
(2) 值方式 return 容器: 移动 -> 高效
4 算法
(1) 通用算法: 函数模板
(2) 策略和操作: 函数对象 ( lambda 表达式)
5 统一的 符号表示法: 类型别名/模板别名
3.1 类: 只考虑 3种
1 具体类 (型)
(1) 思想
其 行为 "就像 内置类型 一样"
complex 像 int
vector/string 像 内置数组
(2) 特征
其 `表现形式 是 定义的一部分`
1) 表现形式
非指针(complex)
指针(vector) -> 指向 自由存储
-> 时空最优
不需要直接访问 类 的 表现形式 的函数, 作 非成员函数
2) => 允许
将 对象 放 栈/静态内存/其他对象中
直接引用对象
创建对象后立即初始化
copy 对象
(3) 代价
表现形式 任何明显变动 -> user 必须重新编译
(4) 容器
RAII
初始化列表 ctor
编译器会自动为列表{...}会创建1个initializer_list对象
class Vector
{
public:
Vector(int s) : elem{ new double[s] }, sz(s)
{
for (int i = 0; i != s; ++i)
elem[i] = 0;
}
~Vector() { delete[] elem; }
double& operator[](int);
int size() const;
private:
double* elem;
int sz;
};
//
class Vector
{
public:
Vector(std::initializer_list<double>);
}
Vector(std::initializer_list<double> lst)
: elem{ new double[lst.size()] }, sz{ lst.size() }
{
std::copy(lst.begin(), lst.end(), elem);
}
2 抽象类 (型)
(1) 思想
`user` 与 类的 实现细节 `完全分离`
优势
只要接口不变, 即使 实现变 => user 也 不需要重新编译
user(Container&) 可以在 完全不了解 抽象类 Container 实现细节(哪个派生类实现 & 如何实现) 的 情况下
使用 抽象类 的 接口函数
分离 接口 与 表现形式, 且 放弃 纯局部变量
|
|
|/
user 对 其 表现形式 一无所知(甚至不知其 大小)
|
|
|/
(2) 特征
必须从 自由存储 为 对象分配空间,
并通过 指针或引用 访问对象
(3) virtual 含义: "可能在 派生类中 重新定义"
|
| = 0
|/
纯虚函数: 派生类 必须定义该 纯虚函数
|
| 含 纯虚函数 的 类
|/
抽象类
负责为派生类提供 接口 => 抽象类 称为 多态类型
不能单纯定义 其 对象
虚函数
用 引用/指针 访问 虚成员函数时, 如何解析到 正确的版本?
答: 虚调用 机制
vptr + vtbl
比 普通函数调用 效率差 <= 25%
// 纯粹的 接口类
class Container
{
public:
virtual double& operator[](int) = 0;
virtual int size() const = 0;
virtual ~Container() {}
};
|
|/
void user(Container& c)
{
const int sz = c.size();
for(int i=0; i!= sz; ++i)
std::cout << c[i] << "\n";
}
class Vector_container: public Container // 继承自 抽象类
{
private:
Vector v; // 具体类 作成员
public:
Vector_container(int s): v(s) {}
~Vector_container(){}
virtual double& operator[](int) { return v[i]; } // 转发: 让 成员 去做
virtual int size() const { return v.size(); }
};
3 类层次
(1)
1) 接口继承
基类 像 派生类 的 `接口` 一样, 通常是 抽象类
抽象基类必须用 `虚dtor`
保证用 抽象基类指针/引用释放派生类对象时,
能正确调用 派生类dtor:
隐式调基类dtor + 成员的dtor
delete pBase 会调用相应派生类的 dtor
派生类可能有需要释放的资源
2) 实现继承
基类 用于 `简化 派生类 实现`, 通常含 成员数据 和 ctor
(2) 合并 容器的遍历 + 对各元素的具体操作
|
| 繁琐 + 依赖
|
| 解决
|/
分离 ... + ...
容器 elem: rawPtr/SP
*elem: 所指 resource
for_all(容器引用, 策略对象)
| |
| |
| |
函数模板 函数对象 / lambda
好处
[1] 函数模板 不依赖于 具体(派生)容器
[2] 策略对象 不 care resource 的 存储方式了 (指针/SP 都行)
处理 resource 的引用
<=> operator() 的 参数为 resource 的引用
// draw_all 是 user 用的 ... 非成员函数
void draw_all(vector<Shape*>& v)
{
for(auto p: v)
p->draw(); // draw 是 resource 的 成员函数
}
void print_all(vector<Shape*>& v)
{
for(auto p: v)
p->print();
}
|
| 分离 容器的遍历 + 对各元素的具体操作
|/
template <typename C, Oper op>
for_all(C& c, Oper op) // for_all 不依赖于 具体(派生类) 容器
{
for(auto& elem: c)
op(*elem); // op 处理 容器中 resource 的引用
} |
|_ _ _ _ _ _ _ _ _ _ _ _
|
void user() |
{ |
vector< unique_ptr<Shape> > v; |
// fill v _ _ _ _ _ _ |
| lambda 很便捷
|/
for_all(v, [](Shape& s) { s.draw(); } );
for_all(v, [](Shape& s) { s.print(); } );
} |\
|_ _ _ _
|
template <typename Resource> |
class Oper |
{ |
// 状态变量: ctor 初始化 |
// T& val; |
public: |
Oper(): {} |
/
void operator()(Resource& r) const
{
cr.draw();
}
};
3.2 copy 和 move
1 copy 容器
copy 的默认含义: 逐成员 copy
(1) 资源句柄类 (string / vector / SmartPointer / thread / fstream ): 字符串句柄 / 动态内存句柄 / 线程句柄 / 文件句柄
负责 通过指针 访问对象
|
|
|/
逐成员 copy: 违反 资源句柄的不变式(resource 只能被释放1次)
副本 析构 -> 原 handle 所管理的 resource 被释放
|
|
|/
copy 的正确含义: 分配空间 + 资源 copy
(2) 同一个类 的 2个对象 互为友员, 对象1的 memFunc 中可直接 用 对象2访问对象2的 private data
A(const A& rhs)
{
x = rhs.x;
}
// copy ctor
Vector::Vector(const Vector& rhs)
: sz(rhs.sz), elem{new double[sz]}
{
for(int i=0; i!=sz; ++i)
elem[i] = rhs.elem[i];
}
// copy assignment
Vector& Vector::operator=(const Vector& rhs)
{
// 1) 分配空间: note 先用 暂存指针 接管
double* p = new double[rhs.sz];
// 2) copy 资源
for(int i=0; i!=sz; ++i)
p[i] = rhs.elem[i];
// 3) 删 旧资源
delete[] elem;
// 4) 接管 新资源
elem = p;
sz = rhs.sz;
return *this;
}
2 move 容器 & 资源管理
(1) 不希望 或 不能 copy, 只希望 移动 资源所有权
不希望 copy Vector<double>
不能 copy Vector<thread>
(2) 移动 后, 源对象 进入的状态 应该允许 运行 dtor, 通常, 也应该允许 被 赋值
移动操作: 允许 资源/对象 从 一个 scope 移动到 另一 scope
// 移动 ctor
Vector::Vector(Vector&& rhs)
: elem(rhs.elem), sz(rhs.sz)
{
rhs.elem = nullptr;
rhs.sz = 0;
}
std::vector<thread> threads;
Vector init(int n)
{
thread t {f};
threads.push_back( move(t) );
//...
Vector vec(n);
for(int i=0; i<vec.size(); ++i)
vec[i] = 10;
return vec;
}
3 抑制操作:=delete
(1) delete 默认 copy 操作, 但 确实希望 copy 类层次 中 某对象
clone() 函数: 返回类型协变 (22.2 节)
通过抽象类(Io_obj) 将 1个类 (Io_circle) 纳入 已有 类层次(Circle)
(2) =delete 机制是通用 的, 可用于 抑制任何操作
3.3 模板
1 参数化 类型
(1) 用 元素类型 参数化 容器类型
模板参数 T
含义: 对 所有类型 T
(2) 为支持 范围 for 循环, 定义 begin()/end()
(3) 模板是 编译时机制, 不会产生 额外 运行时开销
template <typename T>
class Vector
{
private:
T* elem;
int sz;
public:
Vector(int s); // ctor: 建立不变式, 获取资源
~Vector() {delete[] elem; }
// ...
T& operator[](int);
const T& operator[](int) const;
int size() const;
};
// 重载版本
template <typename T>
T* begin(Vector<T>& x)
{
return &x[0];
}
template <typename T>
T* end(Vector<T>& x)
{
return x.begin() + x.size();
}
2 函数模板
3 函数对象
(1) 谓词
返回 bool值 的 函数对象
(2) 策略对象
通用算法 `操作` 含义 的 函数对象
| |
count() Less_than
(3) lambda vs. 函数对象
—————————————————————————————————————————————————————————————————————————————————————————————————————
算法 | 算法
—————————————————————————————————————————————————————————————————————————————————————————————————————
lambda 表达式 | 捕获列表: 捕获方式 | paraList
—————————————————————————————————————————————————————————————————————————————————————————————————————
函数对象 | 按捕获方式 存(内部状态)+传(Ctor 参数) capturelist 成员 | operator()(...)的 paraList
—————————————————————————————————————————————————————————————————————————————————————————————————————
#include <vector>
#include <iostream>
using namespace std;
template<typename C, typename P>
int count(const C& c, P pred)
{
int cnt = 0;
for (const auto& x : c)
if (pred(x))
++cnt;
return cnt;
}
template <typename T>
class Less_than
{
const T val; // 状态变量: ctor 初始化
public:
Less_than(const T& v) : val(v) {}
bool operator()(const T& x) const // 函数调用运算符
{ return x < val; }
};
void f1(vector<int>& vec, int x)
{
cout << count(vec, Less_than<int>(x) ) << endl;
}
void f2(vector<int>& vec, int v)
{
cout << count(vec, [&](const int& x) { return x < v; } ) << endl;
}
int main()
{
vector<int> vec{ 1, 2, 3 };
f1(vec, 3);
f2(vec, 3);
}
4 可变参数模板 ( 详见 28.6 节 )
优点
参数个数+类型 可任意组合
缺点
接口 的 类型检查 复杂
#include <iostream>
template <typename T>
void g(T t)
{
std::cout << t << " ";
}
void f() {} // 3) 递归终止
template <typename T, typename... Tail>
void f(T head, Tail... tail)
{
g(head); // 1) 先处理 第1参数
f(tail...); // 2) 用 剩余参数(tail) 递归调 f()
}
int main()
{
f(1, 2.1, "hello");
}
5 别名
标准库容器 都提供了 value_type 作其 值类型 name
template <typename T>
class Vector
{
public:
using value_type = T;
};
网友评论