美文网首页
14章 namespace

14章 namespace

作者: my_passion | 来源:发表于2022-06-22 22:12 被阅读0次

    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

    相关文章

      网友评论

          本文标题:14章 namespace

          本文链接:https://www.haomeiwen.com/subject/dzsyvrtx.html