Boost 以及 标准库 中的 ref库, 谈谈该库的来龙去脉, 使用技巧.
智能引用, 一种对于cpp现存机制的引用不足的弥补.
C++11引入 std::
的 reference_wrapper
和 boost中谈到的稍稍有些区别, 应该说比 boost 中的更加强大了.
引子
智能引用 ref 或者 cref 或者 reference_wrapper 有如下三个作用:
- 包装你给的对象引用, 其实就是引用的包装提
- 传参数时, 消除复杂对象的拷贝代价
- 将不可拷贝对象转换为可拷贝对象(noncopyable, singleton之类的)
本文涉及到如下类模板:
- std::reference_wrapper
- std::ref
- std::cref
完整定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*------------reference_wrapper*/
template< class T >
class reference_wrapper;
/*------------------ref*/
template< class T >
std::reference_wrapper<T> ref(T& t) noexcept;
template< class T >
std::reference_wrapper<T> ref( std::reference_wrapper<T> t ) noexcept;
//注意不允许右值构造器
template <class T>
void ref(const T&&) = delete;
/*-----------------cref*/
template< class T >
std::reference_wrapper<const T> cref( const T& t ) noexcept;
template< class T >
std::reference_wrapper<const T> cref( std::reference_wrapper<T> t ) noexcept;
template <class T>
void cref(const T&&) = delete;
正文
如果某个调用, 需要一个对象(passed-by-value)作为参数,
比如说容器里面只能存入对象, 对象的指针, 而不能存入引用, 例如:
1 |
|
不用多看, 报错了, 因为vecotr容器内部机制涉及迭代器, 迭代器保留了原容器的指针或者index, 总之容器内部涉及指针操作, 而c++标准规定了, 不允许有引用的指针.
如果编译报错, 那么只能存储对象.
1 | vecotr<reference_wrapper<int>> v2; |
上面的 reference_wrapper
- 容器里面没有存储引用, 而是存储的对象(reference_wrapper对象)
- 并且 reference_wrapper 是代表 int 元素的包装类
那么 “给你一个对象, 你返回一个引用” , 这个包装类怎么写呢? 参考Boost的实现, 可以很简单的给出:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//引用类型的包装类
template<class T>
class reference_wrapper
{
private:
T *t_;
public:
//把对象的地址拿来初始化内部维护的指针std::addressof(t)
explicit refrence_wrapper(T &t): t_(&t) {}
// 隐式转换
operator T&() const { return *t_;} //这个是在原对象的位置传入wrapper对象的基础
T& get() const { return *t_; } //拿到引用对象的真实引用(方便调用原对象的方法), 而不是本包装类对象
T* get_ptr() const { return t_;} //拿到内部维护的指针
}
这样, 在使用智能引用就可以放入容器了, 并且, 原来参数需要原始对象的地方, 由于隐式转换的存在, 也可以调用了.
当然标准库的可能实现会更加安全(阉割了一部分功能), 并且多了 operator()()
的支持:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <class T>
class reference_wrapper {
public:
// types
typedef T type;
// construct/copy/destroy
reference_wrapper(T& ref) noexcept : _ptr(std::addressof(ref)) {}
reference_wrapper(T&&) = delete;
reference_wrapper(const reference_wrapper&) noexcept = default;
// assignment
reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
// access
operator T& () const noexcept { return *_ptr; }
T& get() const noexcept { return *_ptr; }
template< class... ArgTypes >
typename std::result_of<T&(ArgTypes&&...)>::type
operator() ( ArgTypes&&... args ) const {
return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
private:
T* _ptr;
};
注意, 这里没有提供 get_ptr()
方法, 也就是不希望你去使用 内部指针. 当然, 简单使用起来就是不言自明的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using namespace std;
template<typename T>
using wrap = reference_wrapper<T>;
int main(void)
{
int x = 1;
wrap<int> wx(x);
assert( x == wx); //隐式类型转换
wrap<int> wx2(wx); //拷贝构造
assert( wx.get() == wx2);
string str;
wrap<string> ws(str);
ws.get() = "hello world"; //赋值给其真实引用
cout << ((string&)ws).size() << endl;//11
return 0;
}
如果你真的不嫌弃麻烦每次都生成reference_wrapper的对象的话; 其实你可以直接使用它的工厂方法ref, cref生成对象, 直接用:
1 | //std::reference_wrapper<T> ref(T& t) noexcept; |
但是很可惜, Boost中的 is_reference_wrapper<T>::value
判断是否为包装类型对象, 以及 unwrap_reference<T>::type
判断其真实类型(无论是否包装), 在标准库都用不了. 更不要说解包装 unwrap_ref()
了.(解包装调用原来对象的具体方法, 请用get()方法吧)
尾巴
总而言之, 这样一种智能引用机制, 在你原来不能使用引用的地方, 用包装的方式, 实现了另外一个可能, 方便你的调用.