为了讲解C++模板类成员函数如何使用SFINAE特性匹配智能指针,笔者使用最近在实际项目中的应用来进行说明。

最近在项目开发中需要一个根据设定权重来对一组数据进行随机选择的功能,项目中之前应该也有类似的需求,但是都是各自写各自的,没有一个通用的可以共用的代码。

在实际开发中,对一组数据进行随机选择是非常常用的功能,可能是均权随机,也可能是根据设定的权重进行随机,为了达到能通用共用的目的,写成一个模板类,代码如下:

 1template<typename T>
 2class RandomByWeight
 3{
 4public:
 5	RandomByWeight() : m_sum(0)
 6	{
 7	}
 8	
 9	void push_back(const T& v)
10	{
11		m_sum += v.GetWeight();
12		m_vctSum.push_back(m_sum);
13		m_vctData.push_back(v);
14	}
15	
16	const T& RandomPickData() const
17	{
18		int t = Rand(0, m_sum - 1);
19		int l = 0, r = (int)m_vctSum.size() - 1;
20		while (l != r)
21		{
22			int m = (l + r) / 2;
23			if (t >= m_vctSum[m])
24				l = m + 1;
25			else
26				r = m;
27		}
28		return m_vctData[l];
29	}
30	
31	inline const T& operator[](size_t index) const
32	{
33		Assert(index < m_vctData.size());
34		return m_vctData[index];
35	}
36	
37	inline auto begin()
38	{
39		return m_vctData.begin();
40	}
41	
42	inline auto end()
43	{
44		return m_vctData.end();
45	}
46	
47	inline auto size() const
48	{
49		return m_vctData.size();
50	}
51	
52private:
53	int			m_sum;
54	vector<int> m_vctSum;
55	vector<T>	m_vctData;
56};

其中的Rand函数参见 C语言随机数中的实现

使用该类的数据结构T需要实现GetWeight函数。比如:

1struct IDWeight
2{
3	uint16 ID;
4	uint Weight;
5
6	inline uint GetWeight() const { return Weight; }
7};
8
9RandomByWeight<IDWeight> vct;

类中push_back函数使用值传递方式保存到容器中,但是如果数据结构比较大时,开销比较大,所以需要支持智能指针(不能直接使用指针,因为RandomByWeight类不进行内存释放)。

C++11/14标准库不支持智能指针的判断,所以需要自己进行判断:

 1template <typename T>
 2struct is_smart_pointer_helper : public std::false_type {};
 3
 4template <typename T>
 5struct is_smart_pointer_helper<std::shared_ptr<T> > : public std::true_type {};
 6
 7template <typename T>
 8struct is_smart_pointer_helper<std::unique_ptr<T> > : public std::true_type {};
 9
10template <typename T>
11struct is_smart_pointer_helper<std::weak_ptr<T> > : public std::true_type {};
12
13template <typename T>
14struct is_smart_pointer_helper<std::auto_ptr<T> > : public std::true_type {};
15
16template <typename T>
17struct is_smart_pointer : public is_smart_pointer_helper<typename std::remove_cv<T>::type> {};

接下来修改push_back函数,让其既支持普通类,也支持智能指针。有一个非常简单的方式,就是按照普通类的实现方式,新写一个函数push_back_sp,把v.GetWeight(),改为v->GetWeight()即可,在使用智能指针时调用push_back_sp。即:

 1template<typename T>
 2class RandomByWeight
 3{
 4public:
 5	……
 6	void push_back(const T& v)
 7	{
 8		m_sum += v.GetWeight();
 9		m_vctSum.push_back(m_sum);
10		m_vctData.push_back(v);
11	}
12	void push_back_sp(const T& v)
13	{
14		m_sum += v->GetWeight();
15		m_vctSum.push_back(m_sum);
16		m_vctData.push_back(v);
17	}
18	……
19};

这样做虽然达到了目的,但改变了使用者的使用方式。最好的方式还是进行函数重载。为了达到函数重载,需要使用到C++11的SFINAE特性,该特性适用于模板函数,不能用于普通函数,也就是说需要把push_back写成模板函数,然后使用SFINAE特性来让编译器匹配。

使用C++11模板SFINAE特性重写序列化与反序列化一文中有说到,std::enable_if可以使用在以下三个方面:

  • 作为函数参数
  • 作为模板参数
  • 作为返回类型

下面就是作为函数参数的实现:

 1template<typename T>
 2class RandomByWeight
 3{
 4public:
 5	……
 6	template<typename U = T>
 7	void push_back(const typename std::enable_if<!is_smart_pointer<U>::value, U>::type& v)
 8	{
 9		m_sum += v.GetWeight();
10		m_vctSum.push_back(m_sum);
11		m_vctData.push_back(v);
12	}
13
14	template<typename U = T>
15	void push_back(const typename std::enable_if<is_smart_pointer<U>::value, U>::type& v)
16	{
17		m_sum += v->GetWeight();
18		m_vctSum.push_back(m_sum);
19		m_vctData.push_back(v);
20	}
21	……
22};

需要注意的是push_back函数的模板参数需要重新定义一个不同的名称比如U,并且等于类的模板参数T,enable_if中也需要使用新的模板参数名称U,否则可能会编译报错。

下面就是作为模板参数的实现:

 1template<typename T>
 2class RandomByWeight
 3{
 4public:
 5	……
 6	template<typename U = T, typename std::enable_if<!is_smart_pointer<U>::value, U>::type * = nullptr>
 7	void push_back(const T& v)
 8	{
 9		m_sum += v.GetWeight();
10		m_vctSum.push_back(m_sum);
11		m_vctData.push_back(v);
12	}
13
14	template<typename U = T, typename std::enable_if<is_smart_pointer<U>::value, U>::type * = nullptr>
15	void push_back(const T& v)
16	{
17		m_sum += v->GetWeight();
18		m_vctSum.push_back(m_sum);
19		m_vctData.push_back(v);
20	}
21	……
22};

由于push_back函数的返回值是void,所以不能作为返回类型进行重载,否则会出现“对重载函数的调用不明确”的错误,除非调用时特别指定类型(不符合预期,改变了用户的使用方式)

两种方式都可以,看各自的喜好!

参考: https://stackoverflow.com/questions/41853159/how-to-detect-if-a-type-is-shared-ptr-at-compile-time https://blog.csdn.net/sinat_17700695/article/details/109226122