踩坑记---命名空间污染

踩坑

今天运行如下代码时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <cctype>
#include <algorithm>
using namespace std;
int main()
{
/*other code*/
istream_iterator in_iter(fin), eof;
while(in_iter != eof)
{
string s = *in_iter++;
string word;
remove_copy_if(s.begin(), s.end(),
back_inserter(word), ispunct);
}
/*other code*/
}

在mingw492_32下编译提示如下错误:

no matching function for call to ‘remove_copy_if(std::basic_string::iterator, std::basic_string::iterator, std::back_insert_iterator<std::basic_string >, < unresolved overloaded function type>)’ std::back_inserter(word), ispunct);

出坑

提示ispunct是无法解析的重载函数,按照平时使用的经验,ispunctcctype库中定义的一个用来判断一个字符是否为标点符号的函数,这儿怎么回提示重载呢?后来在SO上找到了答案

由于C++在标准库STL中也定义了ispunct函数,定义于std命名空间,且是一个模板函数。由于程序直接通过using namespace std导入了std命名空间,程序默认使用STL库中的ispunct,导致编译器直接使用了未特化的模板函数,并未使用cctype库中的此函数,因此编译无法通过。

正如SO上所说的,为了避免此类问题出现,我们应该禁止使用using指示,即using namespace,而应该使用using声明。由于std命名空间定义了很多标识符,直接导入全部的std命名空间会产生严重的命名空间污染问题。在将程序修改后,编译通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <cctype>
#include <algorithm>

using std::string;

int main()
{
/*other code*/
std::istream_iterator in_iter(fin), eof;
while(in_iter != eof)
{
string s = *in_iter++;
string word;
std::remove_copy_if(s.begin(), s.end(),
std::back_inserter(word), ispunct);
}
/*other code*/
}

填坑

实际上,在谷歌的C++代码规范里面有这样一条

You may not use a using-directive to make all names from a namespace available.

由于命名空间往往是一个黑盒,我们无法直观得查看到命名空间里面定义了哪些名称,因此直接导入一个命名空间是相当危险的事,尤其是在一个多人协作的大型项目中,每个人都有可能往命名空间中添加命名。贸然使用using指示,很有可能导致各种bug,甚至是运行时bug,同时也为后期维护埋下了地雷。