文章目录
目前为止:
- 全局对象在程序启动时候分配,结束时候销毁。
- 局部自动对象,块内定义,离开块销毁。
- 局部static对象第一次使用分配,结束时销毁。
目前只使用过静态内存和栈内存。
静态内存:
- 局部static对象
- 类static数据成员
- 任何函数之外的变量
栈内存: - 函数内的非static对象
接下来就开始介绍堆。
动态内存与智能指针
shared_ptr类
make_shared函数:
shared_ptr的拷贝和赋值
shared_ptr自动销毁所管理的对象
通过析构函数!
shared_ptr还会自动释放相关联的内存
动态对象不再使用就会释放相关联的内存。
// factory returns a shared_ptr pointing to a dynamically allocated object
shared_ptr<Foo> factory(T arg)
{
// process arg as appropriate
// shared_ptr will take care of deleting this memory
return make_shared<Foo>(arg);
}
像下面的这两种种就可以安全的使用:
void use_factory(T arg)
{
shared_ptr<Foo> p = factory(arg);
// use p
} // p goes out of scope; the memory to which p points is automatically freed
shared_ptr<Foo> use_factory(T arg)
{
shared_ptr<Foo> p = factory(arg);
// use p
return p; // reference count is incremented when we return p
} // p goes out of scope; the memory to which p points is not freed
使用动态生存期的类
出于以下三种原因:
- 程序不知道使用多少对象:容器类
- 程序不知道对象的准确类型
- 程序需要在多个对象间共享数据:如共享reference count
下面定义一个共享数据的类(拷贝,赋值,销毁都使用默认的就足够了)
class StrBlob {
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il); //可以列表初始化!
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// add and remove elements
void push_back(const std::string &t) {data->push_back(t);}
void pop_back();
// element access
std::string& front(); std::string& back();
private:
std::shared_ptr<std::vector<std::string>> data;
// throws msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) { }
void StrBlob::check(size_type i, const string &msg) const
{
if (i >= data->size()) throw out_of_range(msg);
}
string& StrBlob::front()
{
// if the vector is empty, check will throw
check(0, "front on empty StrBlob"); return data->front();
}
string& StrBlob::back()
{
check(0, "back on empty StrBlob"); return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob"); data->pop_back();
}
注意
- 理解
make_shared
函数的意义,shared_ptr
的意义。 - 如果将shared_ptr放在容器里,而后不再需要,一定用erase删除,不然内存得不到释放。
shared_ptr
管理栈内指针和堆指针的区别(shared_ptr能管理栈内指针吗?栈内的内存需要管理吗?)不能,不需要- shared_ptr除了默认构造为空和使用make_shared的拷贝构造,还有其他的吗?有,但是还是使用new来初始化,依然是管理动态内存。
直接内存管理
与智能指针不同,自己管理内存的类不能依赖类对象拷贝,赋值,销毁的任何默认定义。
可以使用类型推断
auto p1 = new auto(obj); // p points to an object of the type of obj
// that object is initialized from obj
auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer
动态分配const对象
// allocate and initialize a const int
const int *pci = new const int(1024);
内存耗尽
两种处理方式
// if allocation fails, new returns a null pointer
int *p1 = new int; // if allocation fails, new throws std::bad_alloc
int *p2 = new (nothrow) int; // if allocation fails, new returns a null
pointer
释放错误
下面的例子反映了如果不返回动态内存的指针(非智能指针)会造成什么负担
// factory returns a pointer to a dynamically allocated object
Foo* factory(T arg)
{
// process arg as appropriate
return new Foo(arg); // caller is responsible for deleting this memory
}
void use_factory(T arg)
{
Foo *p = factory(arg); //如果不释放就再也不能被释放
// use p but do not delete it
} // p goes out of scope, but the memory to which p points is not freed!
delete之后重置指针值
delete之后很多都变成空悬指针,我们再指针离开作用域前delete指针再置为nullptr可以部分解决这个问题。所以也没有什么卵用。
注意
- 值初始化和默认初始化在内置类型的含义是不同的,但是在类类型是相同的,都使用默认构造函数。
string *ps1 = new string; // default initialized to the empty string
string *ps = new string(); // value initialized to the empty string
int *pi1 = new int; // default initialized; *pi1 is undefined
int *pi2 = new int(); // value initialized to 0; *pi2 is 0
- new 和delete都有两个步骤:1对象的创建与销毁2分配和释放内存
- 使用new和delete容易1忘记detele.2释放两次3使用已经释放的对象。所以要只使用智能指针!
shared_ptr和new结合使用
智能指针的构造函数是explicit
的,不能隐式转换。
shared_ptr<int> p1 = new int(1024); // error: must use direct
initialization
shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization
reset的使用
类似与反向赋值,通常与unique
结合使用在改变底层对象之前确认自己是不是仅有的用户
if (!p.unique())
p.reset(new string(*p)); // we aren't alone; allocate a new copy
*p += newVal; // now that we know we're the only pointer, okay to change this
object
注意
- 初始化智能指针的普通指针必须指向动态内存。
- 不要混用智能指针和普通指针。(下面的例子很重要)
int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart
pointer
process(shared_ptr<int>(x)); // legal, but the memory will be deleted!
int j = *x; // undefined: x is a dangling pointer!
- 不要使用智能指针的get方法初始化或赋值一个智能指针。get的目的是为了兼容不能使用智能指针的代码。
智能指针与异常
使用智能指针在异常发送的时候也能释放资源!
智能指针和哑类
一些c和c++共同使用的库怎么去释放资源呢?c并没有析构函数。
struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void f(destination &d /* other parameters */)
{
// get a connection; must remember to close it when done
connection c = connect(&d);
// use the connection
// if we forget to call disconnect before exiting f, there will be no way to close c
}
上面的遗忘使用disconnect
释放连接,和使用shared_ptr
避免资源没有释放是等价的。所以我们可以定义自己的delete操作。
自定义shared_ptr的释放操作
默认释放是delete,,但是我们可以自定义删除器。
//自定义的删除器
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection); //另一种初始化方式,第二个参数是可调用对象
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
unique_ptr
unique_ptr不支持拷贝和赋值操作。但支持更换所有权:
p2.reset(p3.release()); // reset deletes the memory to which p2 had
pointed
传递和返回unique_ptr
可以拷贝和赋值一个将要被销毁的unique_ptr。
unique_ptr<int> clone(int p) {
unique_ptr<int> ret(new int (p));
// . . .
return ret;
}
这里编译器执行的是移动赋值和移动构造
传递删除器
unique的删除器会影响到reset(构造)该类型的对象。
void f(destination &d /* other needed parameters */)
{
connection c = connect(&d); // open the connection
// when p is destroyed, the connection will be closed
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
注意
- unique和shared的ptr的管理删除器的方式不同(见16.1.6)
weak_ptr
我们不能直接访问wp,通过lock()
确认对象存在再访问。
if (shared_ptr<int> np = wp.lock()) { // true if np is not null
// inside the if, np shares its object with p
}
核查指针类
作为wp功能的展示,定义一个指针类StrBlobPtr
,保存wp指向StrBlob
的data
成员。使用wp阻止用户访问不存在的vector的企图。
weak_ptr不影响一个给定StrBlob的生存期,但是可以阻止用户访问一个不再存在vector的企图。
// StrBlobPtr throws an exception on attempts to access a nonexistent element
class StrBlobPtr {
public:
StrBlobPtr(): curr(0) { }
//不能用const StrBlob初始化
StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
//定义的运算符解引用和前缀递增
std::string& deref() const;
StrBlobPtr& incr(); // prefix version
private:
// check returns a shared_ptr to the vector if the check succeeds
std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;
// store a weak_ptr, which means the underlying vector might be destroyed
std::weak_ptr<std::vector<std::string>> wptr;
std::size_t curr; // current position within the array
};
//检查指针指向的vector是否存在
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t i, const std::string &msg)
const
{
auto ret = wptr.lock(); // is the vector still around?
if (!ret)
throw std::runtime_error("unbound StrBlobPtr");
if (i >= ret->size())
throw std::out_of_range(msg);
return ret; // 否则返回vector的shared_ptr
}
//下面定义StrBlobPtr的指针操作
std::string& StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr]; // (*p) is the vector to which this object points
}
// prefix: return a reference to the incremented object
StrBlobPtr& StrBlobPtr::incr()
{
// if curr already points past the end of the container, can't increment it
check(curr, "increment past end of StrBlobPtr");
++curr; // advance the current state
return *this;
}
为了能够访问data成员,还要声明friend,且定义begin()
和end()
操作
// forward declaration needed for friend declaration in StrBlob
class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr;
// other members as in § 12.1.1
// return StrBlobPtr to the first and one past the last elements
StrBlobPtr begin() { return StrBlobPtr(*this); }
StrBlobPtr end()
{ auto ret = StrBlobPtr(*this, data->size());
return ret; }
};
begin和end可以看到我们定义的伴随类的作用。那么问题来了,如果不定义伴随类还有别的方法来定义begin和end吗?
动态数组
大多数的类应该使用标准库容器而不是动态分配的数组,自己管理动态内存可能会出现问题,在学完拷贝控制之前不要在类内分配动态内存。
new和数组
初始化动态分配对象的数组
int *pia = new int[10]; // block of ten uninitialized ints
int *pia2 = new int[10](); // block of ten ints value initialized to 0
// block of ten ints each initialized from the corresponding initializer
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
释放
delete [] p;
智能指针和动态数组
标准库提供了可以管理new分配的数组的unique_ptr
的版本。
// up points to an array of ten uninitialized ints
unique_ptr<int[]> up(new int[10]);
//自动调用delete []
up.release(); // automatically uses delete[] to destroy its pointer
//可以使用下标操作了
for (size_t i = 0; i != 10; ++i)
up[i] = i; // assign a new value to each of the elements
注意
- 动态分配空数组也是合法的。
- 注意指向数组的
unique_ptr
的操作与前面的有所不同。 shared_ptr
并不支持管理动态数组,如果想使用的话就提供自己定义的删除器。但是还是不能使用下标运算符。- 智能指针不支持指针算术运算
allocator类
把new和delete中的两个步骤(管理内存,构造/析构对象)解耦,所以有allocate
,deallocate
,construce
,destroy
。
下面是用法:
allocator<string> alloc; // object that can allocate strings
auto const p = alloc.allocate(n); // allocate n unconstructed strings
//如何构造
auto q = p; // q will point to one past the last constructed element
alloc.construct(q++); // *q is the empty string
alloc.construct(q++, 10, 'c'); // *q is cccccccccc
alloc.construct(q++, "hi"); // *q is hi!
//如何析构
while (q != p)
alloc.destroy(--q); // free the strings we actually allocated
//传入的n必须与allocate的一样
alloc.deallocate(p, n);
拷贝和填充未初始化内存的算法
提供了便捷的初始化的方法。有四种:uninitialized_copy/_copy_n/_fill/_fill_n
的算法。接受迭代器!