C++支持动态分配对象,它的生命周期与它们在哪里创建无关,只有当显示的被释放时,这些对象才会被销毁。分配在静态或栈内存中的对象由编译器自动创建和销毁。

new在动态内存中为对象分配空间并返回一个指向该对象的指针,并调用构造函数构造对象;delete接受一个动态对象指针,调用析构函数销毁该对象,并释放与之相关的内存。

那么new、delete和malloc、free有什么区别和联系呢?

1、new/delete是C++的操作符,而malloc与free是C++/C 语言的标准库函数,不在编译器控制权限之内。

2、new做两件事,一是分配内存,二是调用类的构造函数;同样,delete会调用类的析构函数和释放内存。而malloc和free只是分配和释放内存。

3、new建立的是一个对象,而malloc分配的是一块内存;new建立的对象可以用成员函数访问,不要直接访问它的地址空间;malloc分配的是一块内存区域,用指针访问,可以在里面移动指针;new出来的指针是带有类型信息的,而malloc返回的是void指针。

4、new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作。

5、new自动计算需要分配的空间,而malloc需要手工计算字节数
6、new是类型安全的,而malloc不是,比如:

int* p = new float[]; // 编译时指出错误
int* p = malloc(*sizeof(float)); // 编译时无法指出错误

new operator 由两步构成,分别是 operator new 和 construct

7、operator new对应于malloc,但operator new可以重载,可以自定义内存分配策略,甚至不做内存分配,甚至分配到非内存设备上。而malloc无能为力
8、new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
9、malloc/free要库文件支持,new/delete则不要。

new动态分配内存

new无法为其分配的对象命名,而是返回一个指向该对象的指针;默认情况下,动态分配的对象是默认初始化,因此内置对象和组合对象是未初始化的。

int *p1 = new int;//动态分配一个未初始化的无名对象
string *p2 = new string;//默认初始化为空string

养成一个为动态分配的对象初始化的好习惯。

如果提供括号包围的初始化器,则可以通过初始化器来推断对象类型,就可以使用auto,但是这是当括号中仅有单一初始化器的时候才能使用auto。

动态分配一个const对象必须进行初始化。

auto p1 = new auto("haha");
const int *p2 = new const int();

默认情况下,new操作符时内存不足会抛出一个bad_alloc的异常。

但是还有定位new的形式,使得此时不抛出异常,而返回一个空指针。定位new允许我们想new中传递额外的参数。

int *p = new (nothrow) int;//如果失败,返回空指针

delete释放内存

通常,编译器是不能分辨一个指针是指向的静态对象还是动态对象,所以下面的操作是错的,但是编译期检查不出来。

int i = ;
int *p = &i;
delete p;//释放局部变量,错误

注意:new和delete经常出现的三种错误

  1. 忘记释放内存;
  2. 使用已经释放的内存;
  3. 同一块内存释放两次。

delete之后重置指针为空是一个好习惯,悬空指针很危险,通常上面的第二个错误就是悬空指针引起的;但是这样也只是提供有限的保护。

一、智能指针

动态分配内存容易出现问题,因为确保在正确的时间释放内存很困难,我们可能经常忘记释放的内存。于是C++11的标准库中提供了两种智能指针来管理动态对象,它们自己负责动态释放所指向的对象。

分别是:shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。

shared_ptr和unique_ptr都支持的操作如下:

操作 描述

shared_ptr<T> sp

unique_ptr<T> up

空智能指针,可以指向类型为T的对象
p 将p用作一个条件判断,若p指向一个对象,则为true
*p 解引用p,获得它指向的对象
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。

swap(p,q)

p.swap(q)

交换p和q中的指针

1.shared_ptr类

智能指针也是模板,需要提供指向的类型,且默认初始化时,智能指针保存着一个空指针。

shared_ptr<string>sp;//可以指向string的空智能指针

相关操作如下:

操作 描述
make_shared<T>(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化对象。
make_shared<T>p(q)  p是shared_ptr  q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*
p = q  p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放。
p.unique()   若p.use_count()为1,返回true;否则返回false
p.use_count() 返回与p共享对象的智能指针数量;可能很慢,主要用于测试

最安全的分配和使用动态内存的方法是使用make_shared的函数,它和顺序容器的emplace成员类似,它的参数和指向的对象的某个构造函数匹配。

通常可以使用auto来推断类型,方便使用。

shared_ptr<string>sp = make_shared<string>(,'');//指向一个值为“9999999999”的string
auto spInt = make_shared<int>();

每个shared_ptr都会记录有多少个其他的shared_ptr志向相同的对象。因此,可以认为每个shared_ptr都有一个与之关联的计数器,通常称为引用计数。当一个shared_ptr的引用计数为0,它就会自动释放自己所管理的对象。

auto r = make_shared<int>();
r = q;//给r赋值,是它指向另一个对象;则q指向的对象的引用计数会递增,r原来指向的对象的引用计数会递减,如果减为0,则释放该对象。

如果你将shared_ptr存放在一个容器中,而后不再需要全部元素,只使用其中一部分,则要记得使用erase删除不再需要的那些元素。

下面的三种情况下使用动态内存:

  1. 程序不知道自己需要多少个对象;
  2. 程序不知道需要的对象准确类型;
  3. 程序需要在多个对象之间共享数据。
#include <iostream>
#include <string>
#include <memory> //智能指针和动态分配内存
#include <vector>
#include <initializer_list> //初始值列表
#include <stdexcept> 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() { return data->empty(); }
//添加删除元素
void push_back(const std::string &s){ data->push_back(s); }
void pop_back();
//访问元素
std::string& front();
std::string& back();
const std::string& front()const;
const std::string& back() const; private:
std::shared_ptr<std::vector<std::string>> data;
//private 检查函数。
void check(size_type i, const std::string &msg)const;
}; //默认构造函数
StrBlob::StrBlob():
data(std::make_shared<std::vector<std::string>>()) { }
//拷贝构造函数
StrBlob::StrBlob(std::initializer_list<std::string>il):
data(std::make_shared<std::vector<std::string>>(il)) { } void StrBlob::check(size_type i, const std::string &msg)const
{
if(i >= data->size())
throw std::out_of_range(msg);
} const std::string& StrBlob::back()const
{
check(, "back on empty StrBlob");
return data->back();
} //避免代码重复和编译时间问题,用non-const版本调用const版本
//在函数中必须先调用const版本,然后去除const特性
//在调用const版本时,必须将this指针转换为const,注意转换的是this指针,所以<>里面是const StrBlob* 是const的类的指针。
//调用const版本时对象是const,所以this指针也是const,通过转换this指针才能调用const版本,否则调用的是non-const版本,non-const调用non-const会引起无限递归。
//return时,const_cast抛出去除const特性 std::string& StrBlob::back()
{
const auto &s = static_cast<const StrBlob*>(this)->back(); //<span style="color:#FF0000;">auto前面要加const,因为auto推倒不出来const。</span>
return const_cast<std::string&>(s);
} const std::string& StrBlob::front()const
{
check(, "front on empty StrBlob");
return data->front();
} std::string& StrBlob::front()
{
const auto &s = static_cast<const StrBlob*>(this)->front();
return const_cast<std::string&>(s);
} void StrBlob::pop_back()
{
check(, "pop_back on empty StrBlob");
data->pop_back();
} int main()
{
std::shared_ptr<StrBlob>sp;
StrBlob s({"wang","wei","hao"});
StrBlob s2(s);//共享s内的数据
std::string st = "asd";
s2.push_back(st);
//s2.front();
std::cout << s2.front() << std::endl;
std::cout << s2.back() << std::endl;
}

还可以使用new返回指针来初始化智能指针,此时智能指针的构造函数是explicit。

shared_ptr<int>p1(new int());//正确:使用new初始化智能指针
shared_ptr<int>p2 = new int();//错误:必须显示的初始化

但是注意不要将普通的指针和智能指针混合使用,很容易出错。

get()操作

get可以讲指针的权限给代码,但是注意,必须保证代码不会delete指针,才能使用get。永远不要用get去初始化另一个智能指针或给它赋值。

reset()操作

reset会更新引用计数,所以在多个shared_ptr共享对象时,常与unique()一起使用。

异常

使用智能指针,即使程序块过早结束,智能指针类也能确保内存不再需要时将其释放;但是普通指针就不会。

void fun( )
{
int *p = new int();
//如果这时抛出一个异常且未捕获,内粗不会被释放,但是智能指针就可以释放。
delete p;
}

标准很多都定义了析构函数,负责清理对象使用的资源,但是一些同时满足c和c++的设计的类,通常都要求我们自己来释放资源,可以使用智能指针来解决这个问题。

/*有问题*/
connection connect(*destination);
void disconnect(connect);
void f(destination &d)
{
connection c = connect(&d);
disconnect(d);//如果没有调用disconnect,那么永远不会断开连接。
}
//使用智能指针优化,等于自己定义了delete代替本身的delete
connection connect(*destination);
void end_disconnect(connection*p) {disconnect(p);}
void f(destination &d)
{
connection c = connect(&d);
shared_ptr<connection>p(&d, end_connect);//定义自己的删除器
//f退出时,会自动调用end_connect。
}

总结:

智能指针陷阱

  1. 不使用相同的内置指针值初始化(或reset)多个智能指针;//多个智能指针还是单独的指向内置指针的内存,use_count分别为1
  2. 不delete get( )返回的指针;//两次delete释放,智能指针内部也会delete
  3. 不使用get( )初始化或reset另一个智能指针;//free( ): invalid pointer:也是多次释放
  4. 如果你使用get( )返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变得无效了
  5. 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器(删除函数向上面的disconnect( ))。

二、unique_ptr类

由于unique_ptr独占它所指向的对象,因此他不支持普通的拷贝和赋值。

但是,可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique_ptr。

操作 描述

unique_ptr<T> u1

unique_ptr<T,p> u2

空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针;u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr<T,p> u(d) 空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr 释放u所指向的对象,将u置为空
u.release() u放弃对指针的控制权,返回指针,并将u置为空
u.reset() 释放u指向的对象

u.reset(q)

u.reset(nullptr)

如果提供了内置指针q,令u指向这个对象;否则将u置为空

但是有种特殊的拷贝可以支持:我们可以拷贝或赋值一个即将要被销毁的unique_ptr。

unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));//返回一个unique_ptr
}

在早的版本中提供了auto_ptr的类,它有unique_ptr 的部分特性,但是不能在容器中保存auto_ptr, 也不能在函数中返回 auto_ptr, 编写程序时应该使用unique_ptr。

向unique_ptr 传递删除器

#include <memory>
#include <iostream> using namespace std; typedef int connection; connection* connect(connection *d)
{
cout << "正在连接..." << endl;
d = new connection();
return d;
} void disconnect(connection *p)
{
cout << "断开连接..." << endl;
} int main()
{
connection *p,*p2;
p2 = connect(p);
cout << p << endl;
cout << *p2 << endl;
unique_ptr<connection, decltype(disconnect)*>q(p2,disconnect);
//在尖括号中提供类型,圆括号内提供尖括号中的类型的对象。
//使用decltype()关键字返回一个函数类型,所以必须添加一个*号来指出我们使用的是一个指针
}

三、weak_ptr

weak_ptr 是一种不控制对象生存期的智能指针,它指向由一个shared_ptr 管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数,且最后一个指向对象的shared_ptr被销毁,对象就会被释放,不管有没有weak_ptr指向该对象。

weak_ptr操作

操作 描述
weak_ptr<T> w 空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp) 与shared_ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型。
w = p p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象
w.reset() 将w置为空
w.use_count() 与w共享对象的shared_ptr的数量
w.expired() 若w.use_count()为0,返回true,否则返回false
w.lock() 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

当我们创建一个weak_ptr 必须用一个 shared_ptr 初始化。weak_ptr 不会更改shared_ptr 的引用计数。

不能直接使用weak_ptr访问对象,而必须调用lock();引入lock和expired是防止在weak_ptr 不知情的情况下,shared_ptr 被释放掉。
std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为 std::shared_ptr。
std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。
此外,std::weak_ptr 还可以用来避免 std::shared_ptr 的循环引用。

四、动态数组

动态数组并不是数组类型,而是得到一个数组元素类型的指针。

大多数应用应该使用标准库容器而不是动态分配的数组,因为使用容器更简单、更安全。

int *p = new int[get_siae()];//方括号中的大小必须是整数,但不必是常量
int *p = new int[]{,,,,,,,,,};//C++11中可以使用列表初始化
string *sp = new string[]{"ad","fd","xbv"};//初始化器中的元素较少时,剩下的进行值初始化;初始化器中的元素过多时,new失败,不会分配任何内存。

不能用auto分配数组。

动态分配一个空数组是合法的,但是不能解引用。

指向数组的unique_ptr

指向数组的unique_ptr不支持成员访问运算符

操作 描述
unique_ptr<T[]> u u可以指向一个动态分配的数组,数组元素类型为T
unique_ptr<T[]> u(p) u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*
u[i] 返回u拥有的数组中位置i处的对象,u必须指向一个数组

shared_ptr管理动态数组要定义自己的删除器,而且访问元素的时候要是用get()获取内置指针。

shared_ptr<int> sp(new int[], [](int *p){delete[] p;});
sp.reset();//使用上面的lambda表达式释放数组 for(size_t i = ;i != ;++i)
*(sp.get() + i) = i;

五、allocator类

new它将内存分配和对象构造结合到了一起,而allocator类允许我们将分配和初始化分离。

标准库allocator类定义在头文件 <memory>中。它帮助我们将内存分配和构造分离开来,它分配的内存是原始的、未构造的。

类似vector,allocator也是一个模板类,我们在定义一个allocator类类型的时候需要制定它要分配内存的类型,它会根据给定的对象类型来确定恰当的内存大小和对齐位置:

allocator<string> alloc;
auto const p = alloc.allocate(n); // 分配n个未初始化的string

allocator类的操作:

allocator类及其算法
allocator<T> a 定义了一个名为a的allocator对象,可以为类型为T的对象分配内存
a.allocate(n) 分配一段原始的、未构造的内存,保存n个类型为T的对象
a.deallocate(p, n) 释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate成员函数返回的指针,且n必须是创建时候的大小,在destroy之前,用户必须在这块内存上调用destroy函数
a.construct(p, args) p必须是一个类型为T*的指针,只想一块原始内存,args被传递给类型为T的构造函数
a.destroy(p) p为T*类型的指针,此算法对p执行析构函数

allocator分配的内存是未构造的,construct成员函数接受一个指针和零个或多个额外参数,在给定的位置构造一个元素,额外的参数用来初始化对象。这些额外参数必须和类型相匹配的合法的初始化器。 为了使用allocate返回的内存,我们必须用construct构造对象。

auto q = p;
alloc.construct(q++); // *q为空字符串
alloc.construct(q++, , 'c'); // *q为cccccccccc
alloc.construct(q++, "hi"); // *q为hi

还未构造对象的情况下或者是使用原始内存是错误的:

cout << *p << endl; // 正确,使用string的输出运算符
cout << *q << endl; // 错误,q指向未构造的内存

在这些对象使用结束后,我们使用destroy来销毁这些元素:

while (q != p)
alloc.destroy(--q);

元素被销毁后,如果需要将内存归还给系统,就需要调用deallocate函数:

alloc.deallocate(p, n);

传递给deallocate的指针不能为空,必须指向由allocate分配的内存,而且,n必须为allocate分配时的大小。

标准库为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象:
allocator的算法
uninitialized_copy(b, e, b2) 从迭代器b和3
指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中,b2指向的内存必须足够大,能容下输入序列中的元素的拷贝
uninitialized_copy_n(b, n, b2) 从迭代器b指向的元素开始,拷贝n个元素到b2开始的原始内存中
uninitialized_fill(b, e, t) 在迭代器b和e指定的原始内存范围中创建对象,值均为t的拷贝
uninitialized_fill_n(b, n, t) 从迭代器b指向的原始内存地址开始创建n个对象,b必须指向足够大的未构造的原始内存,能容纳给定数量的对象

vector<int> vec{, , , , , };
auto p = alloc.allocate(vec.size() * );
auto q = uninitialized_copy(vec.begin(), vec.end(), p);
uninitialize_fill_n(q, vec.size(), );

uninitialized_copy在给定位置构造元素,函数返回递增后的目的位置迭代器。因此,一个uninitialized_copy调用会返回一个指针,指向最后一个构造的元素之后的位置。

C++基础之动态内存的更多相关文章

  1. 数据结构基础(1)--数组C语言实现--动态内存分配

    数据结构基础(1)--数组C语言实现--动态内存分配 基本思想:数组是最常用的数据结构,在内存中连续存储,可以静态初始化(int a[2]={1,2}),可以动态初始化 malloc(). 难点就是数 ...

  2. 数据结构基础——指针及动态内存分配(malloc)

    一.指针 C语言中的指针是一种数据类型,比如说我们用int *a;就定义了一个指针a,它指向一个int类型的数.但是这个指针是未初始化的,所以,一般的,我们都在创建指针时初始化它,以免出错,在还不吃的 ...

  3. C/C++基础----动态内存

    why 管理较难,忘记释放会内存泄漏,提早释放可能非法引用,重复释放. 为了更容易,更安全的使用动态内存,提供智能指针,其默认初始化保存一个空指针. what shared_ptr允许多个指针指向同一 ...

  4. c++基础(六)——动态内存

    在我们的程序中,静态内存——用来保存局部 static 对象,类 static数据成员,以及定义在任何函数之外的变量.栈内存——用来保存定义在函数内的非 static 对象.分配在  静态内存 或 栈 ...

  5. 转: Linux C 动态内存分配 malloc及相关内容 .

    一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针 ...

  6. c语言基础学习08_内存管理

    =============================================================================涉及到的知识点有:一.内存管理.作用域.自动变 ...

  7. FreeRTOS 动态内存管理

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面 ...

  8. Linux C 动态内存分配--malloc,new,free及相关内容

    一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针 ...

  9. Android JNI编程(五)——C语言的静态内存分配、动态内存分配、动态创建数组

    版权声明:本文出自阿钟的博客,转载请注明出处:http://blog.csdn.net/a_zhon/. 目录(?)[+] 一:什么是静态内存什么又是动态内存呢? 静态内存:是指在程序开始运行时由编译 ...

随机推荐

  1. 记录一则clear重做日志文件的案例

    1.官方文档描述 2.故障报错信息 3.分析解决问题 1.官方文档描述 关于Clearing a Redo Log File的官方文档描述: A redo log file might become ...

  2. cogs 80. 石子归并 动态规划

    80. 石子归并 ★★   输入文件:shizi.in   输出文件:shizi.out   简单对比时间限制:1 s   内存限制:128 MB 设有N堆沙(shi)子排成一排,其编号为1,2,3, ...

  3. 随笔编号-16 MySQL查看表及索引大小方法

    目标:阿里云OS数据库DMS,单个主库最大存储空间为2T.最近公司业务扩展很快,一天数据量达到7.9G左右.要求备份清理历史数据,备份到其他磁盘. 准备: 如果想知道MySQL数据库中每个表占用的空间 ...

  4. Javaweb MVC设计模式

    Javaweb MVC设计模式 一.Java EE开发流程 二.MVC设计模式 什么是MVC? MVC是Model-View-Controller的简称,即模型-视图-控制器. MVC是一种设计模式, ...

  5. 关于工作流引擎ccflow待办分类 研究与技术实现

    关于工作流引擎待办分类 研究与技术实现 关键字:工作流引擎 BPM系统 待办类型 名词:待办 概要介绍:待办就是当前的登录人员要处理的工作,在工作流程里面的节点类型不同,业务场景不同,我们把待办分为如 ...

  6. bzoj 1146 网络管理Network (CDQ 整体二分 + 树刨)

    题目传送门 题意:求树上路径可修改的第k大值是多少. 题解:CDQ整体二分+树刨. 每一个位置上的数都会有一段持续区间 根据CDQ拆的思维,可以将这个数拆成出现的时间点和消失的时间点. 然后通过整体二 ...

  7. 51 nod 石子归并 + v2 + v3(区间dp,区间dp+平行四边形优化,GarsiaWachs算法)

    题意:就是求石子归并. 题解:当范围在100左右是可以之间简单的区间dp,如果范围在1000左右就要考虑用平行四边形优化. 就是多加一个p[i][j]表示在i到j内的取最优解的位置k,注意能使用平行四 ...

  8. hdu 2050 折线分割平面 dp递推 *

    折线分割平面 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Subm ...

  9. PAT L1-043. 阅览室

    L1-043. 阅览室 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 天梯图书阅览室请你编写一个简单的图书借阅统计程序.当读者 ...

  10. Android Activity启动耗时统计方案

    作者:林基宗 Activity的启动速度是很多开发者关心的问题,当页面跳转耗时过长时,App就会给人一种非常笨重的感觉.在遇到某个页面启动过慢的时候,开发的第一直觉一般是onCreate执行速度太慢了 ...