跟我学c++中级篇——模板的模板参数再谈

一、背景

在前面分析过模板的模板参数,当时对类模板中的模板参数进行了形式上的重点说明,应用也举了一个很简单的例子。现在分析一个例程的演进并和实际相结合,看看如何应用。这个例程就是模板参数中要进行某个函数的功能测试,所以先写一个模板函数供调用。但是考虑一下在不同的情况下传递参数会产生什么结果。下面就一步步展开分析一下。

二、模板函数做为模板函数的参数

先看一下标准的简化程序:

//功能测试模板函数
template <typename Fn, typename... Args> void TestTime(Fn &fn, Args... args) {
//此处可处理功能
  fn(std::forward<Args>(args)...);
  //此处可处理功能
}
void Test1(int t) { std::cout << "this test1" << std::endl; }
void Test2() { std::cout << "this test2" << std::endl; }

void test() {
  TestTime(Test1, 10);
  TestTime(Test2);
}
int main()
{
test();
return 0;
}

这个功能函数有点类似于JAVA中的IOC框架中的探针,在执行前和执行后插入相关的代码。这个不是重点,看一下上面的代码,执行没有问题(先忽略Test3和Test4两个模板函数)。然后进一步增加一个模板类做为参数传入:

void Test1(int t) { std::cout << "this test1" << std::endl; }
void Test2() { std::cout << "this test2" << std::endl; }

template <typename T> void Test4(T a) {
  std::cout << "this test4,T is:" << a << std::endl;
}

void test() {
  TestTime(Test1, 10);
  TestTime(Test2);
  TestTime(Test4,10);//此处有编译问题
}
int main()
{
test();
return 0;
}

此时就会报一个编译错误,说模板不匹配。“No matching function for call to ‘TestTime’,candidate template ignored: couldn’t infer template”。这是一个“Semantic(语义)”错误。其被描述为:Build Issue:TestTime(,int)。换句话说,Fn的类型无法确定。
既然不能解析就明确一下,使用下面的方式:

void test() {
  TestTime(Test1, 10);
  TestTime(Test2);
  //问题:如果test函数本身为模板,此处用其模板参数T来替换int,怎么用?看下面的变参模板
  TestTime(std::forward<void(int)>(Test4), 10);
  //TestTime(Test4,10);//替换为上面的方式
}

通过完美转发类型匹配函数声明解决Test4的未解析问题,这样编译器就可以找到相关的函数了。那么再增加一项,当变参模板时如何使用?

template <typename... Args> void Test3(Args... args) {
  std::cout << "this test3,num is:" << sizeof...(args) << std::endl;
}
template <typename T> void Test4(T a) {
  std::cout << "this test4,T is:" << a << std::endl;
}

template <typename... Args> void test(Args... args) {
......

  auto fn = std::forward<void(Args...)>(Test3);
  TestTime(fn, std::forward<Args>(args)...);
  //TestTiem(Test3,args...);//error

  TestTime(std::forward<void(int)>(Test4), 10);
}

此处没有直接在test函数中给定值,而是把其也变成了一个变参函数模板,此时,如何处理函数的调用呢?也就是说,一个函数变参模板传递一个函数变参模板到另外一个函数变参模板中,有点绕啊。
上面的代码已经给出了答案。虽然和Test3比,看上去更复杂一些,但其实原理是一样的。就是让调用的模板参数找到匹配的模板函数,也就是在编译中提示的“无法推断模板参数Fn”。认真分析一下上面的代码,下面看一个单例中的应用 。

三、单例中的应用

下面看例程:

//非变参
template <typename T> class SingleInstance : public NoCopy {
public:
  SingleInstance() = delete;
  ~SingleInstance() = delete;

  static T &instance() {
    std::call_once(once_,SingleInstance::init);
    assert(ins_ != nullptr);
    return *ins_;
  }

private:
  static void init() {
    ins_ = new T();
......
  }
  static void destroy() {
......
    delete ins_;
  }

private:
  static std::once_flag once_;
  static T *ins_;
};
//变参
template <typename T> class SingleInstance : public NoCopy {
public:
  SingleInstance() = delete;
  ~SingleInstance() = delete;

  //此处会出现和上面的Test3一样的现象
  template <typename... Args> static T &instance(Args &&...args) {
    std::call_once(once_,
                   std::forward<void(Args && ...)>(SingleInstance::init),
                   std::forward<Args>(args)...);
    assert(ins_ != nullptr);
    return *ins_;
  }

private:
  template <typename... Args> static void init(Args &&...args) {
    ins_ = new T(std::forward<Args>(args)...);
......
  }
  static void destroy() {
......
    delete ins_;
  }

private:
  static std::once_flag once_;
  static T *ins_;
};
template <class T> T *SingleInstance<T>::ins_ = nullptr;
template <class T> std::once_flag SingleInstance<T>::once_;

这里看一下std::call_once的定义:

  template<typename _Callable, typename... _Args>
    void
    call_once(once_flag& __once, _Callable&& __f, _Args&&... __args)
    {
      // Closure type that runs the function
      auto __callable = [&] {
	  std::__invoke(std::forward<_Callable>(__f),
			std::forward<_Args>(__args)...);
      };

      once_flag::_Prepare_execution __exec(__callable);

      // XXX pthread_once does not reset the flag if an exception is thrown.
      if (int __e = __gthread_once(&__once._M_once, &__once_proxy))
	__throw_system_error(__e);
    }

何其相似啊。而且如果没有函数转发的过程,报得错误和前面的Test3等的错误一样,都是找不到匹配的函数。

四、总结

有几篇总结模板的模板参数的应用了(“模板的模板参数和成员模板”和“模板的模板参数匹配” ),在不同的情况下,各种应用产生的都有不同,所以问题产生的也有不同,其实这里面还是有一些问题的,比如如果Fn是一个类成员函数会是什么情况?思考下,回头有时间分析。

你可能感兴趣的:(C++11,C++,c++,开发语言)