14.0 建议
1 main() 之外 所有 non-local name 都应 置于 namespace
2 定义 namespace 成员
时, 必须用 Namespace::member 直接方式
, 不能用 间接 namespace
3 用 namespace 表达 逻辑结构
4 `接口 和 实现` 用 `分离 的 namespace`
5 `using 指示`
尽量 `不要放在 头文件`
用于
(1) 改进库 (代码转换)
(2) 扩展库 (std 扩展为 Estd)
(3) 局部作用域
6 用 inline namespace 支持 `版本控制`
14.1 组合问题
1 关注点 分离
(1) 模块
是什么?
独立的东西
————————————————————————————————————
细粒度 | 函数 / 类 / namespace
————————————————————————————————————
粗粒度 | 库 / 源文件 / 编译单元
————————————————————————————————————
如何访问 ?
`良好定义` 的 `接口`
(2) 模块化
模块 划分&接口设计
1) 组合 语言特性 ( 函数 / 类 / namespace )
2) 组织 源码 ( 逻辑上 + 物理上 )
14.2 名字空间/namespace
0 namespace 概念
(1)
1) 表达 `一组声明 属于同一 逻辑整体`
2) 形成 scope
(2) namespace 中 的 `实体` 作为 ( namespace 的 ) `成员` 被引用
(3) `从 namespace 外` 引用 `namespace 成员`
4 种方法
————————————————
显式限定
————————————————
using 指示
————————————————
using 声明
————————————————
ADL
————————————————
1 显式限定
`全局作用域` / 类 都是 namespace
类 是包含其 `成员` 的 namespace
—————————————————————————————————————————————————————————————————
3种 namespace | 显式限定
—————————————————————————————————————————————————————————————————
[1] 普通 namespace | namespace名::成员名
—————————————————————————————————————————————————————————————————
[2] 全局作用域 | ::成员名
—————————————————————————————————————————————————————————————————
[3] 类 | static 成员: `类外` 引用
| 类名::成员名
|
| else : `类/派生类 内` 引用, 能访问到时
| 成员名
—————————————————————————————————————————————————————————————————
2 using 声明
(1)
namespace 外 频繁引用 其 成员名
|
| 引入
|/
using 声明
\
\ 将 `代用名 (指 memName )` 引入 scope => 保持 代用名 `局部性`, 以避免混淆
\/
using namespaceName::memName; // using std::string;
(2) 用于 `类层次`
将 `基类成员` 引入 `派生类 scope`
3 using 指示
(1) 想 `不加限定地使用` 某 namespace 中的 name: string str; 而不是 std::string str;
|
|/
using 指示
using namespace namespaceName; // using namespace std;
|
| 问题
|/
`名字冲突: 污染 namespace`
(2) 除 极少特殊情况( `代码转换` 等 )外,
using 指示 不应该放 `头文件 中 global scope`
4 ADL/参数依赖查找: 12.3.3/4节
(1) 含义
call 重载函数 f(x) 时, `自定义类型(X) 的参数 x` 会
为 重载解析(为 f(x) 查找 匹配 version) 引入 scope:
ADL 引入的 scope / ADL 的 `关联名字空间`
...12.3.3 节
_ _ _ _ _ _ _ _
| |
| |/
| namespace Chrono
| {
| class Date { /* ... */ };
|
| std::string format(const Date&);
| bool operator==(const Date&, const std::string&);
| };
|
| // use 函数/format(d) 的 `context/上下文` 区域: 在 `其 caller 之前` 的 `global scope`
|
| void f(Chrono::Date d, int i)
| {
| std::string s = format(d);
| } |
| |/
| format(d) 实参 d 的 类型 Chrono::Date
| |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| ADL 查找
(2) 优点
`不会 污染 namespace`
(3) 应用: 显式限定 繁琐 / using 声明 不便使用 时
1) 运算符
X operator!(X);
struct Z
{
Z operator!();
X f(X x) { return !x; } // 调 ::operator!(X)
};
2) 模板参数
namespace N
{
class A { /**/ };
char f(A);
}
char f(int);
template<typename T>
char g(T t)
{
return f(t);
}
char f(double);
char c1 = g(N::A() ); // N::f(N::A) 被调用
char c2 = g(2.1); // => T 为 double, 但 find scope 只匹配到 f(int) => f(int) 被调用
(4) ADL 与 `重载 优先级: 12.3.4节`
5 名字空间 是 开放的
(1) 含义
物理 可分离, 逻辑上 不分离
成员声明 和 定义 可分离到不同文件
Note: 显式限定 只 定义 新成员
, 还需 (在 namespace 中) add 新声明, 否则 compile error
(2) 意义
namespace 的 接口与实现分离
(3) namespace 别名
无法用于 重新打开 namespace (在 其中 add 声明)
// file1.h
#pragma once
namespace N
{
void f1();
}
// file2.h
#pragma once
namespace N
{
void f2();
}
// file1.cpp
#include "file1.h"
#include <iostream>
void N::f1()
{
std::cout << "N::f1() \n";
}
// file2.cpp
#include "file1.h"
#include "file2.h" // Note: 现在 N 有 2个成员 f1() 和 f2()
#include <iostream>
void N::f2()
{
f1(); // N::f1()
std::cout << "N::f2() \n";
}
// main.cpp
#include "file2.h"
int main()
{
N::f2();
}
14.3 模块化 和 接口
0 正交设计
(1) 模块化
本质
`正交设计`
难点
模块间通信: 安全、便捷、高效
(2) 从 `code` 角度, 设计 `逻辑上的正交`
|
| 方法: 逻辑上正交 / 物理上正交
|/
模块的 `接口 与 实现 分离`
模块间 `交互` 只通过 `模块的接口` 进行
|-- 驱动程序
| |
实线 | | 实线 ( 使用 / 依赖 )
| |
| |/ 虚线(实现)
| 模块1 接口 <- - - - 模块1实现
| /
| / 实线 (这里导致 非完全 正交)
| /
| \/ 虚线
|-->模块2 接口 <- - - 模块2实现
大规模程序 无法做到 完全正交设计
上图中, 模块1的实现 依赖于 模块2的接口 => 非完全正交
1 名字空间 作 模块
接口 (说明)
是 设计行为
应在 实现之前
2 实现
与 `user 程序` 处于 `相同 namespace` 中的 `名字` 不要用 显式限定
3 namespace 作 接口: 分离 用户接口 与 实现者接口
用户接口 与 实现者接口 (即 namespace 名)
[1] 同名: code 物理位置自然提供 独立的 name(文件名)
[2] 不同名: 独立
// (1) 用户接口 与 实现者接口 同名
// file1.h
namespace N // 用户接口
{
void f();
}
// file2.h
namespace N // 实现者接口
{
void f1();
void f2();
}
|
|
|/
// (2) 用户接口 与 实现者接口 不同名
// file1.h
namespace N // 用户接口
{
void f1();
}
namespace N_impl // 实现者接口
{
using namespace N;
void f1();
void f2();
}
14.4 组合 使用 namespace
1 便利性 与 安全性
(1) using 声明 与 using 指示 区别
[1] `using 声明` 是 `声明`
1] + (去掉 namespace 名 的) `同名声明`
-> `重复声明`: compile error
2] `与 普通声明 一样`, `local 声明` name 时,
会 `hide` 同名的 non-local 声明
[2] using 指示 只是 `令 名字` 可访问
+ `同名声明` -> not 重复声明
namespace N
{
int i;
int k;
}
int k;
void f()
{
int i = 0;
using N::i; // 1) 1) compile error: using 声明导致多次声明, i 在 f() 中 重复声明
using N::k; // 2) hide global k
k++; // N::k++
}
void f2()
{
using namespace N;
k++; // 3) k <=> N::k => 二义性: X::k 还是 ::k
}
2 namespace 别名: namespace namespaceAlias = namespaceName;
(1) 短名字 冲突, 长名字 不实用 -> 别名: 解决这两难问题
少用处: 长名字
多用出: 较短别名
(2) update 库版本: 修改 别名 初始化语句, 并 reompile
// (1)
namespace N { /**/ }
N::Sting s = "hello";
N::Sting s2 = "world";
namespace LibiaryVer0001 { /**/ }
LibiaryVer0001::Sting s = "hello";
LibiaryVer0001::Sting s2 = "world";
namespece Lib = LibiaryVer0001;
Lib::Sting s = "hello";
Lib::Sting s2 = "world";
// (2)
namespece Lib = LibiaryVer0001;
|
| mod
|/
namespece Lib = LibiaryVer0002;
Lib::String s = "hello";
3 组合 namespace: 组合 已有接口
来 构造 新接口
namespace 中的 实体(函数/类/变量 等)
的
[1] 定义 必须通过 直接 namespace
[2] 使用
可以通过 间接 namespace
compiler 会从 间接 namespace 中
用 using 指示 引入的 namespace 中 查找
=> 找到 MyN::N1::String
namespace N1
{
class String { /* ... */ };
void f();
}
namespace N2
{
template <class T>
class B { /* ... */ };
}
|
| 组合 namespace
|/
namespace MyN // f() 的 `间接 namespace`
{
using namespace N1;
using namespace N2;
void myF(String&); // MyN::N1::String
}
// === client
void f()
{
MyN::String s = "name1"; // 1) 定义 实体的对象: 可用 `间接 namespace` => MyN::N1::String
}
void MyN::f() // 2) 定义 实体本身: 只能用 `直接 namespace` => error
{
// ...
}
void N1::f() // ok
{
// ...
}
4 组合 与 选择:
解决 名字冲突 和 二义性
(1) 组合机制 (用 using 指示) & 选择机制 (用 using 声明)
namespace name 查找 优先级: 显式声明 & using 声明 > using 指示
(2) 想将 2 个 namespace
中 同名 类型/模板
都引入 新 namespace
1个用 using 声明
, 另1个用 using 别名/重命名
namespace N1
{
class String { /* ... */ };
template <class T>
class Vector { /* ... */ };
}
namespace N2
{
class String { /* ... */ };
template <class T>
class Vector { /* ... */ };
}
|
|
|/
namespace MyN
{
using namespace N1; // N1 中 所有实体: 可访问
using namespace N2;
- - - - using N1::String; // 解决 `潜在 名字冲突` : 使用 N1 中版本
| - - using N2::Vector;
| |
| |
| |_ _template <class T>
| using N1Vec = N1::Vector<T>;
|
|_ _ _ _using N2Str = N2::String; // using 重命名
}
5 namespace 和 重载: 函数重载 跨 namespace -> 应用: 改进库 & 扩展库
(1) 改进库
为 使用 namespace 的 新版本: using 指示
(2) 扩展库
std::sort(v.begin(), v.end() ): 显式指定序列
|
| 封装: 用 Estd
|/
sort(v) : 直接 操作容器
// ====== (1)
// === 旧版本
// A.h
void f(int);
// ...
// B.h
void f(char);
// ...
// user.c
#include "A.h"
#include "B.h"
void g()
{
f('a'); // 调 B.h 中 f()
}
|
| `using 指示`
|/
// === 新版本1
// A.h
namespace A
{
void f(int);
// ...
}
// B.h
namespace B
{
void f(char);
// ...
}
// user.c
#include "A.h"
#include "B.h"
using namespace A;
using namespace B;
void g()
{
f('a'); // 调 B.h 中 f()
}
希望 user.c 完全不变: `using 指示` 放 `头文件` => `名字冲突` 机会增加
// === 新版本2
// ====== (2)
#include <algorithm>
namespace Estd
{ note
using namespace std; —— ——> 删掉 该 using 指示, 仍 正常运行: 因为通过 ADL 还能找到 std 的 sort()
template<class C>
void sort(C& c) { std::sort(c.begin(), c.end() ); }
template<class C, class P>
void sort(C& c, P p) { std::sort(c.begin(), c.end(), p); }
}
// user.c
using namespace Estd; // using 指示
void f()
{
std::vector<int> v {7, 3, 9, 4, 0, 1};
sort(v);
}
6 版本控制
inline namespace: 指定 默认 namespace
7 C++ 允许 namespace 嵌套
8 无名 namespace
compiler 会给
[1] 给 `独一无二` 的 `namespace 名`
[2] 隐含的 using 指示
=> 仅在 `本文件之后 可直接访问` 其 成员: 实践价值不大
namespace
{
void f();
// ...
}
|
| <=> compile
|/
namespace $$$
{
void f();
// ...
}
using namespace $$$;
正交设计.jpg
区分 用户接口 与 实现者接口: 相比 实现者接口, 用户接口 更小.jpg
网友评论