close

<article class="mx-1 my-1">
<h3>目錄</h3>
<ul>
    <li><a href="#%E7%9B%AE%E7%9A%84">目的</a></li>
    <li><a href="#%E5%95%8F%E9%A1%8C%E8%88%87%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88">問題與解決方案</a>
<ul>
    <li><a href="#namespace-%E5%90%8D%E7%A8%B1%E6%B1%A1%E6%9F%93%E8%88%87%E8%A1%9D%E7%AA%81">namespace-解決名稱污染與衝突</a></li>
    <li><a href="#using-declaration/using-directive-%E6%B8%9B%E5%B0%91%E4%B8%8D%E6%96%B7%E5%87%BA%E7%8F%BE%E7%9A%84%E8%B3%87%E6%A0%BC%E4%BF%AE%E9%A3%BE%E8%A9%9E">using declaration / using directive-減少不斷出現的資格修飾詞</a></li>
    <li><a href="#using-declaration/using-directive-%E5%B8%B6%E4%BE%86%E7%9A%84%E5%8D%B1%E9%9A%AA%E6%80%A7">using declaration / using directive-帶來的危險性</a></li>
    <li><a href="#using-declaration/using-directive-%E5%B7%AE%E5%88%A5%E4%B8%8D%E5%83%85%E5%83%85%E5%9C%A8%E6%96%BC%E5%AE%A3%E5%91%8A%E7%AF%84%E5%9C%8D">using declaration / using directive-差別不僅僅在於宣告範圍</a></li>
</ul>
</li>
</ul>
<h3 id="目的">目的</h3>
使用Namespace可以提供、解決以下問題:
<ul>
    <li>名稱污染、衝突</li>
    <li>程式碼模組化</li>
    <li>有邏輯的組織程式碼</li>
</ul>
此外以下為本文使用的名稱(參考[1])所對應之意義
<ul>
    <li>資格修飾詞:std::、::(全域)</li>
    <li>using declaration:using std::string;</li>
    <li>using direction:using namespace std;</li>
</ul>

<!--more-->

<h3 id="問題與解決方案">問題與解決方案</h3>
<h4 id="namespace-名稱污染與衝突">namespace-解決名稱污染與衝突</h4>
當我們定義函式庫時沒有使用Namespace,這個函式庫被使用者#include後,將會污染使用者#include時的程式碼區塊。 下面的函式,只要#include,就可以使用:
<pre>----// my_lib.cpp
    bool isEqual(int a, int b) {
        return a == b;
    }

----// main.cpp
    #include 
    #include "my_lib.cpp"

    int main() {
        std::cout << (isEqual(0, 1) ? "true" : "false") << std::endl;
        
        return 0;
    }

----output:
    false
</pre>
但當我們引入多個函式庫,並且他們擁有同樣命名名稱的函式時,將會引起編譯錯誤,因為不知道要使用哪一個:
<pre>----// my_lib.cpp
    bool isEqual(int a, int b) {
        return a == b;
    }

+---// another_lib.cpp
+   bool isEqual(int a, int b) {
+       return a != b;  // 一個荒謬的函式
+   }

----// main.cpp
    #include 
    #include "my_lib.cpp"
+   #include "another_lib.cpp"

    int main() {
+       std::cout << (isEqual(0, 1) ? "true" : "false") << std::endl;

        return 0;
    }
----編譯錯誤,重複定義
</pre>
此時我們可以利用namespace加上資格修飾詞,明確知道要使用哪個程式碼
<pre>----// my_lib.cpp
+   namespace my_lib {
        bool isEqual(int a, int b) {
            return a == b;
        }
+   }

----// another_lib.cpp
+   namespace another {
        bool isEqual(int a, int b) {
            return a != b;  // 一個荒謬的函式
        }
+   }

----// main.cpp
    #include 
    #include "my_lib.cpp"
    #include "another_lib.cpp"

    int main() {
+       std::cout << (my_lib::isEqual(0, 1) ? "true" : "false") << std::endl;
+       std::cout << (another::isEqual(0, 1) ? "true" : "false") << std::endl;

        return 0;
    }

----output:
    false
    true
</pre>
其實這種方式我們早已習慣,因為全域、結構(struct, class, enum/enum class[2]等)也算是一種Namespace:
<pre>----// main.cpp
    #include 
    
    char c = 'g';

    struct s {
        static const char c = 's';
        // 為何要使用static修飾詞,這使得c屬於s,而不屬於由s定義的物件,方便舉例
        // 為何要使用const修飾詞,請參考[3]
    };

    int main() {
        char c = 'l';

        std::cout << "local c is char: " << c << std::endl;
        std::cout << "global c is char: " << ::c << std::endl;
        std::cout << "c in struct s is char: " << ::s::c << std::endl;

        return 0;
    }

----output:
    local c is char: l
    global c is char: g
    c in struct s is char: s
</pre>
需注意:名稱覆蓋不限定在同一種宣告,例如:
<pre>----// main.cpp
    #include 

    void max();

    int main() {
        int max = 2147483647;

        std::cout << "local max is integer: " << max << std::endl;
        std::cout << "want to invoke global max function: " << max() << std::endl;

        return 0;
    }

----編譯錯誤:max(),max是一個整數,不是一個函式
</pre>
<h4 id="using-declaration/using-directive-減少不斷出現的資格修飾詞">using declaration / using directive-減少不斷出現的資格修飾詞</h4>
在上述的程式碼中,我們可以看見使用iostream的cout等,都須要使用資格修飾詞std::,我們可以使用using declaration來減少這種限制:
<pre>----// my_lib.cpp
    #include 

    namespace my_lib {
+       using std::cout;

        int print() {
+           cout << 123 << std::endl;
        }
    }

----// main.cpp
    #include "my_lib.cpp"

    int main() {
        my_lib::print();

        return 0;
    }

----output:
    123
</pre>
仔細觀察上一個例子,我們可以發現std::endl,還是得使用資格修飾詞std::,若我們想運用std所有的成員,我們可以運用using directive:
<pre>----// my_lib.cpp
    #include 

    namespace my_lib {
+       using namespace std;

        int print() {
+           cout << 123 << endl;
        }
    }

----// main.cpp
    #include "my_lib.cpp"

    int main() {
        my_lib::print();

        return 0;
    }

----output:
    123
</pre>
<h4 id="using-declaration/using-directive-帶來的危險性">using declaration / using directive-帶來的危險性</h4>
我們必須特別小心地運用using declaration / using directive,以下我們以using directive(using declaration的影響是一樣的,只是看你宣告了哪個成員)一一舉例說明:
<ol>
    <li>將namespace std宣告於namespace my_lib底下,這意味著我只要存取my_lib即可存取std成員:
<pre>----my_lib.cpp(or my_lib.hpp)
    #include 

    namespace my_lib {
+       using namespace std;

        void print() {
            cout << "123" << endl;
        }
    }

----main.cpp
    #include "my_lib.cpp"

    int main() {
+       my_lib::cout << "456" << endl;

        return 0;
    }

----output
    123
    456
</pre>
</li>
    <li>將namespace std宣告在namespace global,這種狀況更糟糕,限制更小,只要#include該檔案,即可使用std成員:
<pre>----my_lib.cpp(or my_lib.hpp)
    #include 

+   using namespace std;

    namespace my_lib {
        void print() {
            cout << "123" << endl;
        }
    }

----main.cpp
    #include "my_lib.cpp"

    int main() {
+       cout << "456" << endl;

        return 0;
    }

----output
    123
    456
</pre>
</li>
    <li>這下子看起來好像不管在哪裡使用using directive都會有危險性?不,我們再重新看一下上面第二點的描述,你發現了嗎?「只要#include該檔案,就可以使用std成員」,那我們不要#include就好了呀!,咦!不#include,那我們要怎麼取得該檔案?利用標頭/實作檔,我們將using directive儘量放在實作檔,就可以大致解決問題了:
<pre>+---my_lib.hpp
+   namespace my_lib {
+       void print();
+   }

----my_lib.cpp
+   #include "my_lib.hpp"
    #include 

    using namespace std;

    void my_lib::print() {
        cout << "123" << endl;
    }

----main.cpp
+   #include "my_lib.hpp"

    int main() {
-   //  cout << "456" << endl;            // 錯誤,iostream作用域沒有延伸到my_lib.hpp
-   //  std::cout << "456" << std::endl;  // 同上

        return 0;
    }

----編譯錯誤
</pre>
可是!我們可能並不能完全解決名稱污染的問題,因為我們有些情況下必須將檔案#include在標頭檔(下面明確提供函式getMatrix介面給使用者),這時候就算我們沒有使用using directive,#include該檔案的使用者,仍然可以自行宣告std從而存取到vector成員(如下),良好的模組分離終究是需要相當技術的:
<pre>+---my_lib.hpp
+   #include 
    namespace my_lib {
+       std::vector getMatrix();
        void print();
    }

----main.cpp
    #include "my_lib.hpp"

    int main() {
+       std::vector vec{1};
        
        return 0;
    }

----output
    (編譯成功)
</pre>
</li>
</ol>
<h4 id="using-declaration/using-directive-差別不僅僅在於宣告範圍">using declaration / using directive-差別不僅僅在於逐個、全部宣告</h4>
using declaration會實際的宣告(真是人如其名)一個名稱在它所處的scope,另外,如同宣告區域變數一樣,也會覆蓋外圍的namespace(全域):
<pre>----main.cpp
    #inlcude 
    using namespace std;

    namespace eg {
        int i = 0;
    }

    int i = 2;

    int main() {
        using eg::i;
        
    //  int i = 1;  // 錯誤,已被using declaration宣告
        
        cout << i;
        cout << ::i;
        cout << eg::i;

        return 0;
    }
----output:
    0
    1
    0
</pre>
using directive則僅是表示你在所處的scope可以使用某namespace的成員,而這種情況,如果同名將會導致模稜兩可:
<pre>----main.cpp
    #include 
    using namespace std;

    namespace eg {
        char c = 'e';
    }

    char c = 'g';

    int main () {
        char c = 'l';         // 若沒有宣告local c,將會造成模稜兩可

        using namespace eg;   // using directive 與 宣告local c 的順序不重要

        cout << c << endl;
        cout << ::c << endl;
        cout << eg::c << endl;

        return 0;
    }
----output:
    l
    g
    e
</pre>
<h3>參考資料</h3>
<ol>
    <li>TCPL(The C++ Programming Language)國際中文版 - Bjarne Stroustrup</li>
    <li><a href="https://stackoverflow.com/questions/18335861/why-is-enum-class-preferred-over-plain-enum/18335954">Why is enum class preferred over plain enum?</a></li>
    <li><a href="https://stackoverflow.com/questions/9656941/why-cant-i-initialize-non-const-static-member-or-static-array-in-class">Why I can't initialize static data members in class?</a></li>
</ol>
</article>

arrow
arrow

    Ernest 發表在 痞客邦 留言(0) 人氣()