不要在自定义的命名空间中引入头文件

问题描述

最近敲代码的时候,遇到一个有趣的问题,特此记录下。
问题:C++中的头文件没有在文件最开头引入,而是在自定义的命名空间中引入,这会导致哪些问题
比如下面的问题示例:

#include <iostream>

#define NUMERIC

namespace math {

#ifdef NUMERIC
#   include <numeric>
#endif

}

int main(int argc, char* argv[])
{
    std::cout<<"hello world"<<std::endl;
}

编译这个代码,会出现如下报错:

➜  g++ -o ns ns.cpp
In file included from /usr/include/c++/9/numeric:62,
                 from ns.cpp:8:
/usr/include/c++/9/bits/stl_numeric.h: In function ‘_OutputIterator math::std::partial_sum(_InputIterator, _InputIterator, _OutputIterator)’:
/usr/include/c++/9/bits/stl_numeric.h:253:24: error: expected nested-name-specifier before ‘iterator_traits’
  253 |       typedef typename iterator_traits<_InputIterator>::value_type _ValueType;
      |                        ^~~~~~~~~~~~~~~
......
......

/usr/include/c++/9/bits/stl_numeric.h:349:14: error: ‘move’ is not a member of ‘math::std’; did you mean ‘std::move’?
  349 |    __value = _GLIBCXX_MOVE(__tmp);
      |              ^~~~~~~~~~~~~
......
......

PS:我没能完全解决这个问题,问题有些难。

问题分析

报错一:expected nested-name-specifier before

当我们使用搜素引擎,搜素expected nested-name-specifier before这个问题的时候,我目前只找见nested-name-specifier - stackoverflow,里面的提示是删除typename。这不是我想要的内容,因为不应该修改标准库的内容。

所以,我们只好自己分析这个问题。下面为报错处的上下文代码。

template<typename _InputIterator, typename _OutputIterator,
	   typename _BinaryOperation>
    _OutputIterator
    partial_sum(_InputIterator __first, _InputIterator __last,
		_OutputIterator __result, _BinaryOperation __binary_op)
    {
      typedef typename iterator_traits<_InputIterator>::value_type _ValueType;

      // concept requirements
      __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
      __glibcxx_function_requires(_OutputIteratorConcept<_OutputIterator,
				                         _ValueType>)
      __glibcxx_requires_valid_range(__first, __last);

      if (__first == __last)
	return __result;
      _ValueType __value = *__first;
      *__result = __value;
      while (++__first != __last)
	{
	  __value = __binary_op(_GLIBCXX_MOVE_IF_20(__value), *__first);
	  *++__result = __value;
	}
      return ++__result;
    }

std::partial_sum函数作用有点类似于求前缀和,整个函数使用模板方式实现。

typedef typename iterator_traits<_InputIterator>::value_type _ValueType;引起的报错。其中的typename,在模板的定义中, can be used to declare that a dependent qualified name is a type. (即,iterator_traits<_InputIterator>::value_type的类型是不确定的,依赖_InputIterator。)

然后,我就不知道了。万一哪天需要,我翻看完《C++ Templates中文版》,我再回来填坑。

填坑中…

  1. 假定T是一个模板类型参数,当编译器遇到类似T::mem这样的代码时,它不会知道mem是一个类型成员还是一个static数据成员,直至实例化时才会知道。但是,为了处理模板,编译器必须知道名字是否表示一个类型。

    因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点。

报错二:‘move’ is not a member of ‘math::std’; did you mean ‘std::move’

这个问题,大体是域名空间的嵌套与调用。

__value = _GLIBCXX_MOVE(__tmp);_GLIBCXX_MOVE宏定义如下所示:

#define _GLIBCXX_MOVE(__val) std::move(__val)

由于外层嵌套的域名,原本需要调用的是std::move,变成了math::std::move

这个问题该怎么解释呢?先挖个坑,回头来填。

回来填坑中…

详细参见《C++ Primer》18.2 命名空间。

  1. 在通常情况下,我们不把#include放在命名空间内部。如果我们这么做了,隐含的意思是把头文件中所有的名字定义成该命名空间的成员。(上面那么做,意味着我们试图将numeric头文件中std函数嵌套在命名空间math中。)

  2. 嵌套的命名空间同时是一个嵌套的作用域,它嵌套在外层命名空间的作用域中。嵌套的命名空间中的名字遵循的规则与往常类似:内层命名空间声明的名字将隐藏外层命名空间声明的同名成员。在嵌套的命名空间中定义的名字只在内层命名空间中有效,外层命名空间中的代码要想访问它必须在名字前添加限定符。

    此时,我们会奇怪。上面代码,由于头文件包含在命名空间见中,应该会包含math::std::move函数的。内部调用的是std::move函数,应该会查找到math::std::move,而不应该报错的。

    我们使用g++ -E进行头文件展开,可以明显看到有math::std::move函数。
    在这里插入图片描述
    命名空间中的内容,调用该命名空间的嵌套命名空间中的函数,不需要在函数前面加上完整的作用域。为此,我做了下验证。
    在这里插入图片描述
    此时,发生上面报错的原因,则可能是由于move函数的特殊性
    下面两点是相关性的两点,并不能直接说明问题。详细问题的解释,我不清楚。

  3. 当我们给函数传递一个类类型的对象时,除了在常规的作用域查找外还会查找实参类所属的命名空间。这一例外对于传递类的引用或指针的调用同样有效。查找规则的这个例外允许概念上作为类接口一部分的非成员函数无须单独的using声明就能被程序使用。
    在这里插入图片描述
    使用g++ -E我们会发现:#inlcude<iostream>会引入std::move;在math命名空间中包含# include <numeric>会引入math::std::mov
    在这里插入图片描述

解决方法

按照这条准则写代码:头文件不要包含在命名空间中,在开头调用

#include <iostream>

#define NUMERIC

#ifdef NUMERIC
#   include <numeric>
#endif

namespace math {
//
}

int main(int argc, char* argv[])
{
    std::cout<<"hello world"<<std::endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

da1234cao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值