14章 namespace

2022-06-22  本文已影响0人  my_passion

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
上一篇下一篇

猜你喜欢

热点阅读