开源中文网

您的位置: 首页 > 编程开发 > C++语言编程 > 正文

在g++中使用“概念”(concept)检查模板参数

来源:  作者:

Created: Fang lungang 09/27/2008 Modified: Fang lungang 09/28/2008 16:09>

home

真正的C++程序员应该都用过template,至少是STL。相信大家对模板的编译错误提示印象深刻。

模板编译还有一个特点:没用到就不会去编译(实例化),也就不会检查错误。

“概念(Concept)”本身就是泛型编程的一个重要概念。我的理解:泛型编程就是将算法与具体的数据类型分离;根据算法要求抽象出一个“概念”,任何满足这个“概念”的数据类型的数据都可用这个算法来操作。但是,C++语言本身并不支持“概念”这个特性(新标准里可能会加入)。所以在代码中无法明确体现出对模板参数的要求。

怎么办?在g++带的STL(来自SGI)里部分采用了boost的“Boost Concept Check Library (BCCL)”。

本文不讨论“concept”的理论。只是稍微注释一下boost如何用现有的语言特性模拟的部分“概念”机制,以便大家理解、使用。实现很简单,主要内容都在两个文件里: bits/boost_concept_check.h, bits/concept_check.h。我们先看看具体实现,理解之后就知道怎么用了。用起来也很简单。

在函数内检查concept

我们看到 std::min_element 的源码里有对 concept 的要求(注:本文引用的源码都经删节、修改):

template<typename _ForwardIter>
  _ForwardIter
  min_element(_ForwardIter __first, _ForwardIter __last)
  {
    // concept requirements
    __glibcpp_function_requires(_ForwardIteratorConcept<_ForwardIter>)
    __glibcpp_function_requires(_LessThanComparableConcept<
      typename iterator_traits<_ForwardIter>::value_type>)
    // ...
  }

顺藤摸瓜

#ifndef _GLIBCPP_CONCEPT_CHECKS // lgfang:所以编译的时候
                                // 要-D_GLIBCPP_CONCEPT_CHECKS。但如果
                                // 直接包含bits/boost_concept_check.h就
                                // 不需要了,那里面没这个条件预编译。

#define __glibcpp_function_requires(...)
#define __glibcpp_class_requires(_a,_b)

#else // the checks are on

#include <bits/boost_concept_check.h>

#define __glibcpp_function_requires(...)                                 \
            __gnu_cxx::__function_requires< __gnu_cxx::__VA_ARGS__ >();
#define __glibcpp_class_requires(_a,_C)                                  \
            _GLIBCPP_CLASS_REQUIRES(_a, __gnu_cxx, _C);
#endif // enable/disable
namespace __gnu_cxx { // lgfang: 都算作gnu扩展,所以放在这个namespace

#define _IsUnused __attribute__ ((__unused__))
    //lgfang:__attribute__ ((__unused__))是个gnu扩展,用来修饰变量,让
    //编译器别因为这个变量没被用到而报错

template <class _Concept>
inline void __function_requires()
//lgfang: 这是一个函数,所以可在函数中使用(被调用),但不能在类定义里
//使用。所以其名字里的 "function" 就是这意思。
{
  void (_Concept::*__x)() _IsUnused = &_Concept::__constraints;
  // lgfang: 定义了一个变量 __x, 变量的类型是: “_Concept”类的成员函
  // 数,且既没有参数也没有返回值的。

  // lgfang: 我们就没打算用 __x,所以用 __attribute__ ((__unused__)) 修
  // 饰它。

  // lgfang: 定义变量 __x 就足以让编译器实例化 _Concept::__constraints
  // 了。所以我们要做的是在 _Concept::__constraints 里想办法让编译器实
  // 例化所需的接口。下面会以 LessThanComparableConcept 为例。

  // lgfang: 已经达到目的了,所以什么操作都不需要做。因而对运行效率影响
  // 很小。
}

template <class _Tp>
struct _LessThanComparableConcept // lgfang: 一个concept类的例子。
{
  void __constraints() { // lgfang: 从上面分析可知,必须得是这个名字
    __aux_require_boolean_expr(__a < __b); // 这是具体的要求;下文还有例子。
  }
  _Tp __a, __b; // lgfang: 必须是成员变量。如果挪到 __constraints 里就
                // 增加了约束条件:有默认构造函数(不论是自己写的还是编
                // 译器构造的)。参见:
                // http://www.boost.org/doc/libs/1_36_0/libs/concept_check/creating_concepts.htm
};

}

在类定义内检查concept

相关代码如下:

template <typename _Tp, typename _Alloc = allocator<_Tp> >
  class deque : protected _Deque_base<_Tp, _Alloc>
{
  // concept requirements
  __glibcpp_class_requires(_Tp, _SGIAssignableConcept)
  // ...
}

#define __glibcpp_class_requires(_a,_C)                                  \
            _GLIBCPP_CLASS_REQUIRES(_a, __gnu_cxx, _C);

#define _GLIBCPP_CLASS_REQUIRES(_type_var, _ns, _concept) \
  typedef void (_ns::_concept <_type_var>::* _func##_type_var##_concept)(); \
  template <_func##_type_var##_concept _Tp1> \
  struct _concept_checking##_type_var##_concept { }; \
  typedef _concept_checking##_type_var##_concept< \
    &_ns::_concept <_type_var>::__constraints> \
     _concept_checking_typedef##_type_var##_concept


template <class _Tp>
struct _SGIAssignableConcept
{
  void __constraints() {
    _Tp __b(__a) _IsUnused;
    __a = __a;                        // require assignment operator
    __const_constraints(__a);
  }
  void __const_constraints(const _Tp& __b) {
    _Tp __c(__b) _IsUnused;
    __a = __b;              // const required for argument to assignment
  }
  _Tp __a;
};

有了之前的基础,这里只分析宏_GLIBCPP_CLASS_REQUIRES就可以了吧。一行一行来:
 

1
这个宏接受三个参数。注意,用法和 __function_requires 不一样。
2
typedef 一个类型以便后面使用。类型的名字由宏的参数拼接而成。
3、4
定义一个模板类
5、6、7
再typedef一个类型。这次其实并不是想要用这个类型。只是通过这

种方式让编译器以_type_var作为模板参数实例化上一步的模板类,从而达到检查concept的目的。

注意,这个宏没有任何运行时的操作,所以不影响程序的运行效率,只是增加编译期的工作量。

</src>

创建新的'Concept'

了解了原理,我们就会定义自己的“concept”了(不过最好先确认一下 boost_concept_check.h 里是否已经有定义了。)

#include <bits/boost_concept_check.h>
// 虽然 boost_concept_check.h 里说不能直接用它,但好像没有别的办法。

using namespace std;

// 发现不同版本g++里宏的名称稍有不同,再包一层:(。
#define my_function_requires(...) __gnu_cxx::__function_requires<MySpace::__VA_ARGS__ >()
#define my_class_requires(a, C) _GLIBCXX_CLASS_REQUIRES(a, MySpace, C)

namespace MySpace {  // 必须放在一个namespace,好像麻烦点。其实不然,因
                     // 为实际项目本来就应当把代码包含在一个namespace里。
    template <typename T>
    struct HasValueConcept { // 用关键字struct而非class就为少敲几个键
                      // ("public:")
        void __constraints() {
            T b; // 确定有默认构造函数
            a.getVal(); // 确定有成员函数 getVal
            // 或者更严格一点,把函数原型也定死:
            // const char* (T::*x)() = &T::getVal;
            a.name; // 也可以检查成员变量,但一般不应该这么做。
        }
        T a;
    };

}

template <typename T>
void dumpVal (T& t) {
    my_function_requires(HasValueConcept<T>); // 在函数中检查HasValueConcept
    t.getVal();
}

template <typename T>
struct C {
    my_class_requires(T, HasValueConcept); // 在类定义检查HasValueConcept
    T a;
};

struct MyFun { // 修改这个类,看看编译器会报些什么错
    const char* getVal() {return "hello";}
    char* name;
};

int main(){
    MyFun obj;
    dumpVal(obj);
    C<MyFun> c;
    return 0;
}

// g++ testconcept.cc

更复杂的用法请参看 boost 的文档和 SGI STL 的源码。

用处

这个机制的用处没有我原先想象的那么大。不过毕竟它的代价也很小,所以何乐而不为呢?我能想到的好处有:

  • 错误提示会“好看” 一点点;
  • 编译器做的检查会多一些;
  • 代码更清晰一些,对类型的要求一目了然。

Reference

Tags:概念 模板 参数
关于开源中文网 - 联系我们 - 广告服务 - 网站地图 - 版权声明