scoped_array
scoped_array很像scoped_ptr,它包装了new[]操作符(不是单纯的new)在堆上分配的动态数组,为动态数组提供了一个代理,保证可以正确地释放内存。
scoped_array相当于C++11标准中管理数组对象用法的unique_ptr。
类摘要
template<class T>
class scoped_array //noncopyable
{
public:
explicit scoped_array (T * p = 0) ; //构造函数
~scoped_array(); //析构函数
void reset(T * p = 0); //重置智能指针
T & operator [](std::ptrdiff_t i) const; //重载operator[]
T* get() const; //获得原始指针
explicit operator bool() const; //显式bool值转型
void swap(scoped_array & b); //交换指针
};
scoped_array的接口和功能几乎与scoped_ptr是相同的(甚至还要少一些),主要特点是:
1)构造函数接受的指针 p 必须是new[]的结果,而不能是new表达式的结果;
2)没有*、->操作符重载,因为scoped_array持有的不是一个普通指针;
3)析构函数使用delete[]释放资,而不是delete;
4)提供operator[]操作符重载,可以像普通数组一样用下标访问元素;
5)没有begin()、end()等类似容器的迭代器操作函数。
用法
scoped_array 与scoped_ptr源于相同的设计思想,故而用法非常相似:它只能在被声明的作用域内使用,不能拷贝、赋值。唯一不同的是,scoped_array包装的是new[]产生的指针,并在析构时调用delete[]一因为它管理的是动态数组,而不是单个动态对象。
通常scoped_array的创建方式是这样的:
scoped_array<int> sa(new int[100]); //包装动态数组
scoped_array重载了operator[],因此它用起来就像是一个普通的数组,但因为它不提供指针运算,所以不能用“数组首地址+N”的方式访问数组元素:
sa[0] = 10; //正确用法,使用operator[]
*(sa + 1) = 20; //错误用法,不能通过编译
在使用重载的operator[]时要小心,scoped_array不提供数组索引的范围检查,如果使用了超过动态数组大小的索引或者是负数索引将引发未定义行为。
#include <boost/smart_ptr.hpp>
using namespace boost;
int main()
{
int* arr = new int[100]; //一个整数的动态数组
scoped_array<int> sa(arr); //scoped_array对象代理原始动态数组
fill_n(&sa[0], 100, 5); //可以使用标准库算法赋值数据
sa[10] = sa[20] + sa[30]; //用起来就像是个普通数组
} //这里scoped_array被自动析构
//释放动态数组资源
对比unique_ptr
template <class T, class D>
class unique_ptr<T[],D> { //注意:对数组形式特化
public:
typedef some_define pointer; //内部类型定义
typedef T element_type;
constexpr unique_ptr() noexcept; //构造函数
explicit unique_ptr(pointer p) noexcept;
~unique_ptr(); //析构函数
T & operator[](size_t i) const; //重载operator[]
pointer get() const noexcept; //获得原始指针
explicit operator bool() const noexcept; //显式bool值转型
pointer release() noexcept; //释放指针的管理权
void reset(pointer p) noexcept; //重置智能指针
void swap(unique_ptr& u) noexcept; //交换指针
unique_ptr(const unique_ptr&) = delete; //使用delete禁用拷贝
unique_ptr& operator=(const unique_ptr&) = delete;
};
unique_ptr的数组对象用法与scoped_array基本相同,但模板参数中需要声明为数组类型:
同样的,头文件<boost/smart_ptr/make_unique.hpp>
里提供了创建数组对象unique_ptr的工厂函数make_unique():
auto a = boost::make_unique<int[]>(5); //5个元素的动态数组
a[0] = 100; //operator[]访问第一个元素
a[4] = 500; //operator[]访问最后一个元素
a[5] = 1000; //数组越界,未定义行为!
使用建议
scoped_array没有给程序增加额外的负担,用起来很方便轻巧。它的速度与原始数组同样快,很适合那些习惯于用new操作符在堆上分配内存的程序员。但scoped_array的功能很有限,不能动态增长,没有边界检查,也没有迭代器支持,不能搭配sTE算法,仅有一个纯粹的“裸”数组接口。而且,我们应当尽量避免使用new[]操作符,它比 new更可怕,是许多错误的来源,因为
int *p = new int[10]; //创建动态数组
delete p; //错误地使用delete删除数值指针!
这样的代码完全可以通过编译,无论是编译器还是程序员都很难区分出 new[]和new分配的空间,而错误地运用delete将导致资源异常。
在需要动态数组的情况下我们应该使用std::vector,它比 scoped_array提供了更多的灵活性,而只付出了很小的代价。由于vector有丰富的成员函数来操纵数据,能够使代码更加简单明了,易于维护。
除非对性能有非常苛刻的要求,或者编译器不支持标准库,否则本书不推荐使用scoped_array,它只是为了与老式c风格代码兼容而使用的类,它的出现往往意味着你的代码中存在着隐患。
代码示例
#include <iostream>
using namespace std;
#include <boost/smart_ptr.hpp>
using namespace boost;
//
void case1()
{
int* arr = new int[100];
scoped_array<int> sa(arr);
fill_n(&sa[0], 100, 5);
sa[10] = sa[20] + sa[30];
cout << sa[10] << endl;
cout << sa[99] << endl;
}
//
void case2()
{
unique_ptr<int[]> up(new int[10]);
assert(up);
up[0] = 10;
cout << up[0] << endl;
up.reset();
assert(!up);
}
//
int main()
{
case1();
case2();
}