C++ using 用法
Tags: cpp
Published: 2024-06-21 09:49:09

C++ 的 `using` 有兩個用法, 一種是把名稱空間中的名稱宣告到目前的名稱空間中的 [`using 宣告語法`](https://en.cppreference.com/w/cpp/language/namespace#Using-declarations)；另一種則是把指定名稱空間內的名稱全部變成編譯器可視的 [`using namespace 編譯器指引`](https://en.cppreference.com/w/cpp/language/namespace#Using-directives)。

## `using 宣告`

`using 宣告` 的作用等於是在目前名稱空間中宣告一個指定名稱空間內名稱的別名, 例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
}

using namespace std;
int main(void)
{
   using outer::var;
   cout << var << endl;
}
```

由於在 `main` 中利用 `using outer::var` 宣告了 `outer` 名稱空間內的 `var` 名稱, 所以只要提到 `var`, 指的就是 `outer` 名稱空間內的 `var`。執行結果就顯示 10： 

```
10
```

### 重複宣告

由於 `using 宣告` 會在目前空間中宣告名稱, 所以要避免在目前空間中重複宣告同一個名稱, 例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
}

using namespace std;
int main(void)
{
   int var = 20;
   using outer::var;
   cout << var << endl;
}
```

由於在 `main` 中已經有宣告 `var`, 所以當利用 `using` 再宣告 `outer` 名稱空間內的 `var` 時, 就會導致重複宣告的問題, 編譯器會發出如下的錯誤訊息：

```
C:\cpp\test.cpp: In function 'int main()':
C:\cpp\test.cpp:12:17: error: 'int outer::var' conflicts with a previous declaration
   12 |    using outer::var;
      |                 ^~~
C:\Users\meebo\code\cpp\test.cpp:11:8: note: previous declaration 'int var'
   11 |    int var = 20;
      |        ^~~
```

## `using namespace 編譯器指引`

`using 宣告` 每次只會引入指定名稱空間內的一個名稱, 如果覺得麻煩, 也可以改用 `using namespace 編譯器指引`, 它可以一次引入名稱空間中的所有名稱, 例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
}

using namespace std;
int main(void)
{
   using namespace outer;
   cout << var << endl;
}
```

使用 `using namespace 編譯器指引` 時只要指定名稱空間, 不需要指定名稱空間中的名稱, 之後就可以直接使用該名稱空間內的所有名稱, 因此實際顯示的就是 `outer` 內的 `var`：

```
10
```

### 名稱遮蔽

要注意的是, `using namesapce 編譯器指引` 實際的作用相當於把指定名稱空間內的所有名稱放到涵蓋該名稱空間定義與 `using namesapce 編譯器指引` 所在名稱空間的最內層名稱空間, 以剛剛的例子來說, 因為 `outer` 名稱空間位於全域名稱空間, 而 `using namespace outer` 位於 `main` 函式的名稱空間內, 要涵蓋這兩個名稱空間, 就是全域名稱空間了, 也就是說, 當編譯到 `using namesapce outer` 後, 全域名稱空間內會有一個 `var` 名稱, 這個名稱實際上指的就是 `outer` 中的 `var`。

因此, 如果在 `main` 中有定義 `var`, 它就會遮蔽掉全域名稱空間中的 `var`。如果在 `main` 讀取 `var` 就會讀到定義在 `main` 中的那一個, 例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
}

using namespace std;
int main(void)
{
   int var = 20;
   using namespace outer;
   cout << var << endl;
}
```

執行的結果就會是：

```
20
```

### 名稱混淆

由於 `using namespace outer` 會把 outer 名稱空間內的名稱放到 (注意這不是宣告) 全域的名稱空間中, 如果你有定義同名的全域名稱, 編譯時就會出錯, 因為現在在全域名稱空間內有兩個同樣的名稱 `var`, 編譯器不知道你到底要用哪一個？例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
}

int var = 20;

using namespace std;
int main(void)
{
   using namespace outer;
   cout << var << endl;
}
```

編譯時錯誤訊息如下：

```
C:\cpp\test.cpp: In function 'int main()':
C:\cpp\test.cpp:14:12: error: reference to 'var' is ambiguous
   14 |    cout << var << endl;
      |            ^~~
C:\cpp\test.cpp:8:5: note: candidates are: 'int var'
    8 | int var = 20;
      |     ^~~
C:\cpp\test.cpp:5:8: note:                 'int outer::var'
    5 |    int var = 10;
      |        ^~~
```

你可以看到錯誤訊息並不是重複宣告, 而是編譯器對於 `var` 到底是你宣告的全域變數還是 `outer` 內的 `var` 感到混淆 (ambiguous), 無法判斷。

為了讓編譯器知道你指的是哪一個, 你必須使用 `::` 運算子指明, 如果是你宣告的全域名稱, 就不需要指定名稱空間, 例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
}

int var = 20;

using namespace std;
int main(void)
{
   using namespace outer;
   cout << ::var << endl;
}
```

編譯器就知道要用的是全域變數, 執行結果如下：

```
20
```

如果要用的是 `outer` 內的 `var`, 就要指明名稱空間：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
}

int var = 20;

using namespace std;
int main(void)
{
   using namespace outer;
   cout << outer::var << endl;
}
```

這樣就會顯示正確的結果：

```
10
```

### 多層名稱空間也會有名稱混淆的情況

如果有多層的名稱空間, 那麼在使用 `using namespace 編譯指引` 時也可能會發生類似的名稱混淆情況, 例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
   namespace inner
   {
      using namespace outer;
      int var = 5;
   }
}

using namespace std;
int main(void)
{
   using namespace outer::inner;
   cout << var << endl;
}
```

由於在 `inner` 中也利用了 `using namespace outer` 引入了 `outer` 中的名稱, 所以在全域的名稱空間中就會放入一個其實是 `outer` 內 `var` 的 `var`。當你在 `main` 中又再引入 `outer::inner` 的名稱時, 全域名稱空間內現在就會有兩個 `var` 了。編譯時就會看到熟悉的錯誤訊息：

```
C:\cpp\test.cpp: In function 'int main()':
C:\cpp\test.cpp:17:12: error: reference to 'var' is ambiguous
   17 |    cout << var << endl;
      |            ^~~
C:\cpp\test.cpp:5:8: note: candidates are: 'int outer::var'
    5 |    int var = 10;
      |        ^~~
C:\cpp\test.cpp:9:11: note:                 'int outer::inner::var'
    9 |       int var = 5;
      |           ^~~

```

錯誤訊息清楚地告訴你發生混淆的各是哪一個名稱空間內的名稱。為了讓編譯器能夠辨別, 你就必須指明名稱空間, 要注意的是只要能夠區分就好, 不一定要寫出完整的名稱空間階層, 例如：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
   namespace inner
   {
      using namespace outer;
      int var = 5;
   }
}

using namespace std;
int main(void)
{
   using namespace outer::inner;
   // cout << outer::inner::var << endl;
   cout << inner::var << endl;
}
```

這樣編譯器就知道要用的是 `outer::inner` 的 `var`, 如果要用 `outer` 內的 `var`, 就必須這樣寫：

```cpp
#include <iostream>

namespace outer
{
   int var = 10;
   namespace inner
   {
      using namespace outer;
      int var = 5;
   }
}

using namespace std;
int main(void)
{
   using namespace outer::inner;
   cout << outer::var << endl;
}
```

## 引入名稱的有效範圍

不論是使用哪一種 `using` 語法, 引入的名稱有效範圍都只有在 `using` 所在的名稱空間, 例如：

```cpp
#include <iostream>

namespace outer
{
    int var = 10;
}

using namespace std;
int main(void)
{
    {
        using namespace outer;
    }
    cout << inner::var << endl;
}
```

編譯時就會因為要使用 `var` 時, 已經脫離 `using` 所在的區塊, 所以會編譯錯誤：

```
C:\cpp\test.cpp:14:13: error: 'var' was not declared in this scope; did you mean 'outer::var'?
   14 |     cout << var << endl;
      |             ^~~
      |             outer::var
C:\cpp\test.cpp:5:9: note: 'outer::var' declared here
    5 |     int var = 10;
      |         ^~~
```

告訴你 `var` 是沒有宣告的名稱。

## 結語

雖然 `using namespace 編譯器指引` 可以一次把指定名稱空間內的名稱都引入, 用起來非常便利, 不過如同本文所看到, 它會讓整個名稱解析變得複雜, 因此實務上都建議採用 `using 宣告` 的方式一次引入一個你要使用的名稱。

另外, 在標頭檔中也請不要在全域的名稱空間中使用 `using`  語法, 否則容易造成重複宣告或是名稱混淆的問題, 因為你永遠搞不清楚到底哪些標頭檔中引入了哪些名稱空間。