正文
关于这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> #include <memory> #include <string>
std::unique_ptr<std::string> demo(const char *s) { std::unique_ptr<std::string> temp(new std::string(s)); return temp; }
int main() { std::unique_ptr<std::string> ps; ps = demo("Uniquely special");
std::cout << *ps << std::endl;
return 0; }
|
我们很容易理解,智能指针来源于模板类,对于上面的 demo()
,它返回了一个对象 temp
。按照一般认知,在不考虑前面讨论的返回值优化的前提下,demo()
在返回前会移动构造创建一个临时栈外的对象,然后通过这个对象再移动赋值给 ps,然后析构掉 temp
,这个过程依赖于移动赋值函数。智能指针之所以智能,就是做了一些特定的设计:
1 2 3 4 5 6
| #ifdef _LIBCPP_CXX03_LANG unique_ptr(unique_ptr const&) = delete; unique_ptr& operator=(unique_ptr const&) = delete; #endif ...
|
在头文件 memory
中,可以看到上面的代码禁止了复制构造函数和复制赋值构造函数,因此上面的提到的过程就会发生。但是这会导致空指针异常吗?按照之前的经验,返回的对象 temp
会被销毁,从而导致 ps
对象是空的。
这时候,就要了解智能指针设计的精妙了:
1 2 3 4 5 6 7 8 9 10 11
| template <class _Up, class _Ep, class = _EnableIfMoveConvertible<unique_ptr<_Up, _Ep>, _Up>, class = _EnableIfDeleterAssignable<_Ep> > _LIBCPP_INLINE_VISIBILITY unique_ptr& operator=(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT { reset(__u.release()); __ptr_.second() = _VSTD::forward<_Ep>(__u.get_deleter()); return *this; } ...
|
尽管智能指针禁止了复制构造函数和复制赋值构造函数,但是提供了移动赋值运算符重载函数。这将会在赋值的时候,将右值的资源转让给左值。因此上面的程序是没有任何错误与风险的。
再来看看关于智能指针的一个程序:
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
| #include <iostream> #include <memory> #include <vector>
std::unique_ptr<int> make_int(int n) { return std::unique_ptr<int>(new int(n)); }
void show(std::unique_ptr<int> &pi) { std::cout << *pi << ' '; }
int main() { std::vector<std::unique_ptr<int>> vp(10); for (int i = 0; i < vp.size(); ++i) { vp[i] = make_int(rand() % 1000); }
for (auto& ptr : vp) { show(ptr); }
return 0; }
|
在给show()
函数传递参数时,如果按值而不是按引用传递unique_ptr
对象,会导致编译器尝试复制unique_ptr
对象,这是不允许的,因为unique_ptr
禁止了复制构造函数。因此,如果按值传递unique_ptr
对象给show()
函数,编译器将发出错误提示。
另外,for_each()
函数中的lambda表达式会将vp
中的每个元素传递给show()
函数,而lambda表达式默认情况下是按值捕获参数的。因此,如果show()
函数接受的参数是按值而不是按引用传递的,那么for_each()
语句将会产生编译错误,因为它会尝试复制构造unique_ptr
对象。
为了解决这个问题,可以将show()
函数的参数声明为const std::unique_ptr<int>&
,以便按引用传递参数,或者使用std::move()
将unique_ptr
对象作为右值传递给show()
函数。