最近被问到了C++内存池的问题,其中不免涉及到在指定内存地址调用对象构造函数以及显示调用对象析构函数的情况。

C++中new的用法

new是C++中用于动态内存分配的运算符,在C语言中一般使用malloc函数

(1)plain new顾名思义就是普通的new,就是我们惯常使用的new。分配内存,调用构造函数,在C++中是这样定义的:

 void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void *) throw();

plain new在分配失败的情况下,抛出异常std::bad_alloc而不是返回NULL,因此通过判断返回值是否为NULL是徒劳的。

 #include "stdafx.h"
#include <iostream>
using namespace std;
char *GetMemory(unsigned long size)
{
char *p=new char[size];//分配失败,不是返回NULL
return p;
} int main()
{
try
{
char *p=GetMemory(10e11);// 分配失败抛出异常std::bad_alloc
//...........
if(!p)//徒劳
cout<<"failure"<<endl;
delete [] p;
}
catch(const std::bad_alloc &ex)
{
cout<<ex.what()<<endl;
} return ;
}

(2)nothrow new是不抛出异常的运算符new的形式。nothrow new在失败时,返回NULL。定义如下:

 void * operator new(std::size_t,const std::nothrow_t&) throw();
void operator delete(void*) throw(); 
 #include "stdafx.h"
#include <iostream>
#include <new>
using namespace std;
char *GetMemory(unsigned long size)
{
char *p=new(nothrow) char[size];//分配失败,是返回NULL
if(NULL==p)
cout<<"alloc failure!"<<endl;
return p;
} int main()
{
try
{
char *p=GetMemory(10e11);
//...........
if(p==NULL)
cout<<"failure"<<endl;
delete [] p;
}
catch(const std::bad_alloc &ex)
{
cout<<ex.what()<<endl;
}
return ;
}

(3)placement new意即“放置”,这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。定义如下:

 void* operator new(size_t,void*);
void operator delete(void*,void*);

palcement new的主要用途就是反复使用一块较大的动态分配的内存来构造不同类型的对象或者他们的数组。placement new构造起来的对象或其数组,要显示的调用他们的析构函数来销毁,千万不要使用delete。

 #include "stdafx.h"
#include <iostream>
#include <new>
using namespace std;
class ADT
{
int i;
int j;
public:
ADT()
{
}
~ADT()
{
}
}; int main()
{
char *p=new(nothrow) char[sizeof(ADT)+];
if(p==NULL)
cout<<"failure"<<endl;
ADT *q=new(p) ADT; //placement new:不必担心失败
// delete q;//错误!不能在此处调用delete q;
q->ADT::~ADT();//显示调用析构函数
delete []p;
return ;
}

使用placement new构造起来的对象或数组,要显式调用它们的析构函数来销毁(析构函数并不释放对象的内存),千万不要使用delete.这是因为placement new构造起来的对象或数组大小并不一定等于原来分配的内存大小,

使用delete会造成内存泄漏或者之后释放内存时出现运行时错误。
 

另:

当使用new运算符定义一个多维数组变量或数组对象时,它产生一个指向数组第一个元素的指针,返回的类型保持了除最左边维数外的所有维数。例如:

int *p1 = new int[10];

返回的是一个指向int的指针int*

int (*p2)[10] = new int[2][10];

new了一个二维数组, 去掉最左边那一维[2], 剩下int[10], 所以返回的是一个指向int[10]这种一维数组的指针int (*)[10].

int (*p3)[2][10] = new int[5][2][10];  new了一个三维数组, 去掉最左边那一维[5], 还有int[2][10], 所以返回的是一个指向二维数组int[2][10]这种类型的指针int (*)[2][10].

#include<iostream>

#include <typeinfo> 

using namespace std;

 int main() { 

int *a = new int[]; 

int *b = new int[]; 

int (*c)[] = new 

int[][]; 

int (*d)[] = new int[][]; 

int (*e)[][] = new int[][][];

 int (*f)[][] = new int[][][];

 a[] = ; 

 b[] = ; //运行时错误,无分配的内存,b只起指针的作用,用来指向相应的数据

 c[][] = ;

d[][] = ;//运行时错误,无分配的内存,d只起指针的作用,用来指向相应的数据 

e[][][] = ; 

f[][][] = ;//运行时错误,无分配的内存,f只起指针的作用,用来指向相应的数据 

cout<<typeid(a).name()<<endl;

 cout<<typeid(b).name()<<endl;

 cout<<typeid(c).name()<<endl; 

cout<<typeid(d).name()<<endl; 

cout<<typeid(e).name()<<endl;

 cout<<typeid(f).name()<<endl; 

delete[] a; delete[] b; delete[] c; 

delete[] d; delete[] e; delete[] f; 

}   

输出结果:

 int *

int *

int (*)[]

int (*)[]

int (*)[][]

int (*)[][]

深入学习文献C++new用法深层剖析

C++显示调用析构函数

一、文章来由

现在在写一个项目,需要用到多叉树存储结构,但是在某个时候,我需要销毁这棵树,这意味着如果我新建了一个树对象,我很可能在某处希望将这个对象的声明周期终结,自然会想到显示调用析构函数,但是就扯出来这么大个陷阱。

二、原因

在了解为什么不要轻易显示调用析构函数之前,先来看看预备知识。 
为了理解这个问题,我们必须首先弄明白“堆”和“栈”的概念。

1)堆区(heap) —— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

2)栈区(stack) —— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

我们构造对象,往往都是在一段语句体中,比如函数,判断,循环,还有就直接被一对“{}”包含的语句体。这个对象在语句体中被创建,在语句体结束的时候被销毁。问题就在于,这样的对象在生命周期中是存在于栈上的。也就是说,如何管理,是系统完成而程序员不能控制的。所以,即使我们调用了析构,在对象生命周期结束后,系统仍然会再调用一次析构函数,将其在栈上销毁,实现真正的析构。

所以,如果我们在析构函数中有清除堆数据的语句,调用两次意味着第二次会试图清理已经被清理过了的,根本不再存在的数据!这是件会导致运行时错误的问题,并且在编译的时候不会告诉你!

三、显示调用带来的后果

如果硬要显示调用析构函数,不是不可以,但是会有如下3条后果:

1)显式调用的时候,析构函数相当于的一个普通的成员函数

2)编译器隐式调用析构函数,如分配了对内存,显式调用析构的话引起重复释放堆内存的异常

3)把一个对象看作占用了部分栈内存,占用了部分堆内存(如果申请了的话),这样便于理解这个问题,系统隐式调用析构函数的时候,会加入释放栈内存的动作(而堆内存则由用户手工的释放);用户显式调用析构函数的时候,只是单纯执行析构函数内的语句,不会释放栈内存,也不会摧毁对象

用如下代码表示:

例1:

class aaa
{
public:
aaa(){}
~aaa(){cout<<"deconstructor"<<endl; } //析构函数
void disp(){cout<<"disp"<<endl;}
private:
char *p;
}; void main()
{
aaa a;
a.~aaa();
a.disp();
}

分析:

这样的话,显式两次destructor,第一次析构相当于调用一个普通的成员函数,执行函数内语句,显示第二次析构是编译器隐式的调用,增加了释放栈内存的动作,这个类未申请堆内存,所以对象干净地摧毁了,显式+对象摧毁

例2:

class aaa
{
public:
aaa(){p = new char[1024];} //申请堆内存
~aaa(){cout<<"deconstructor"<<endl; delete []p;}
void disp(){cout<<"disp"<<endl;}
private:
char *p;
}; void main()
{
aaa a;
a.~aaa();
a.disp();
}

分析:

这样的话,第一次显式调用析构函数,相当于调用一个普通成员函数,执行函数语句,释放了堆内存,但是并未释放栈内存,对象还存在(但已残缺,存在不安全因素);第二次调用析构函数,再次释放堆内存(此时报异常),然后释放栈内存,对象销毁

四、奇葩的错误

系统在什么情况下不会自动调用析构函数呢?显然,如果对象被建立在堆上,系统就不会自动调用。一个常见的例子是new…delete组合。但是好在调用delete的时候,析构函数还是被自动调用了。很罕见的例外在于使用布局new的时候,在delete设置的缓存之前,需要显式调用的析构函数,这实在是很少见的情况。

我在栈上建树之后,显示调用析构函数,对象地址任然存在,甚至还可以往里面插入节点。。。

其实析构之前最好先看看堆上的数据是不是已经被释放过了。

////////////////a.hpp
#ifndef A_HPP
#define A_HPP #include <iostream>
using namespace std; class A
{
private:
int a;
int* temp;
bool heap_deleted;
public:
A(int _a);
A(const A& _a);
~A();
void change(int x);
void show() const;
}; #endif ////////////a.cpp #include "a.hpp"
A::A(int _a): heap_deleted(false)
{
temp = new int;
*temp = _a;
a = *temp;
cout<< "A Constructor!" << endl;
} A::A(const A& _a): heap_deleted(false)
{
temp = new int;
*temp = _a.a;
a = *temp;
cout << "A Copy Constructor" << endl;
} A::~A()
{
if ( heap_deleted == false){
cout << "temp at: " << temp << endl;
delete temp;
heap_deleted = true;
cout << "Heap Deleted!\n";
}
else {
cout << "Heap already Deleted!\n";
} cout << "A Destroyed!" << endl;
} void A::change(int x)
{
a = x;
} void A::show() const
{
cout << "a = " << a << endl;
} //////////////main.cpp #include "a.hpp"
int main(int argc, char* argv[])
{ A a(1);
a.~A();
a.show();
cout << "main() end\n";
a.change(2);
a.show(); return 0;
}

五、小结

所以,一般不要自作聪明的去显示调用析构函数。

C++中new的用法及显示调用析构函数的更多相关文章

  1. 关于c++显示调用析构函数的陷阱

    版权声明:欢迎转载,注明出处就好!如果不喜欢请留言说明原因再踩哦,谢谢,我也可以知道原因,不断进步!!   目录(?)[+]   一.文章来由 现在在写一个项目,需要用到多叉树存储结构,但是在某个时候 ...

  2. 由STL所想到的 C++显示调用析构函数

    在STL中的容器中的析构函数中,会经常调用destroy()这个函数 在STL中  destroy()被重载了 这点在这里到不去讨论 这里贴最简单的那个版本 template<class T&g ...

  3. verilog中defparam的用法 (verilog调用底层模块(只改变)参数的传递)

    当一个模块引用另外一个模块时,高层模块可以改变低层模块用parameter定义的参数值,改变低层模块的参数值可采用以下两种方式: 1)defparam 重定义参数 语法:defparam path_n ...

  4. (转载)C++中, 构造函数和析构函数能不能被显示调用?

    (转载)http://blog.csdn.net/zhangxinrun/article/details/6056321 代码: view plaincopy to clipboardprint?#i ...

  5. 显示调用C++中构造函数和析构函数(有什么弊端)

    1.C++中, 构造函数和析构函数可以被显示调用. 显示调用默认构造函数的语法: a.A::A();(不能写成a.A();) , 显示调用非默认构造函数的语法: a.A::A(7);(不能写成a.A( ...

  6. Qt显示调用vs中的dll

    网上看到很多文章写调用vc的dll,但我尝试了总是出问题,下面结合参考别人的文章,实现了Qt显示调用vs中c接口的dll. 具体直接上代码: vs中的代码: TMax.h: #ifdef TMAX # ...

  7. WPF中StringFormat的用法--显示特定位数的数字

    原文:WPF中StringFormat的用法--显示特定位数的数字 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/huangli321456/art ...

  8. jQuery中Animate进阶用法(一)

    jQuery中animate的用法你了解多少呢?如果仅仅是简单的移动位置,显示隐藏,哦!天哪你在浪费资源!因为animate太强大了,你可以有很多意想不到的用法!让我们一起研究一下吧~~ 首先要了解j ...

  9. oracle中to_date详细用法示例(oracle日期格式转换)

    这篇文章主要介绍了oracle中to_date详细用法示例,包括期和字符转换函数用法.字符串和时间互转.求某天是星期几.两个日期间的天数.月份差等用法 TO_DATE格式(以时间:2007-11-02 ...

随机推荐

  1. ref:LDAP入门

    ref:https://www.jianshu.com/p/7e4d99f6baaf LDAP入门 首先要先理解什么是LDAP,当时我看了很多解释,也是云里雾里,弄不清楚.在这里给大家稍微捋一捋. 首 ...

  2. [代码审计]eml企业通讯录管理系统v5.0 存在sql注入

    0x00 前言 上周五的时候想练练手,随便找了个系统下载下来看看. 然后发现还有VIP版本,但是VIP要钱,看了一下演示站,貌似也没有什么改变,多了个导入功能?没细看. 搜了一下发现这个系统,压根就没 ...

  3. Swift2.0语言教程之函数的返回值与函数类型

    Swift2.0语言教程之函数的返回值与函数类型 Swift2.0中函数的返回值 根据是否具有返回值,函数可以分为无返回值函数和有返回值函数.以下将会对这两种函数类型进行讲解. Swift2.0中具有 ...

  4. 1035 Password (20)(20 point(s))

    problem To prepare for PAT, the judge sometimes has to generate random passwords for the users. The ...

  5. Openstack_通用模块_Oslo_vmware 创建 vCenter 虚拟机快照

    创建虚拟机快照 vSphere Create Snapshot 文档 Snapshot 是虚拟机磁盘文件(VMDK)在某个点及时的复本.包含了虚拟机所有虚拟磁盘上的数据状态和这个虚拟机的电源状态(on ...

  6. Acer宏碁暗影骑士3进阶版无法进入系统引导修复

    1.刚开机时,按住alt不放,不停点击F10进入恢复系统: 2.点击疑难解答,选择cmd: 3.以下是cmd命令,//注释不要复制 c: //进入C盘 cd windows cd system32 b ...

  7. PHP 笔记——面向对象编程知识点

    类是属性和方法的集合,是面向对象编程方式的核心和基础,通过类可以将零散的用于实现某项功能的代码进行有效管理. 类是由class关键字.类名和成员组成的,类名不区分大小写. 在类中定义常量使用关键字 c ...

  8. poj3537 Crosses and Crosses 博弈论

    大意: 给定一个\(1 * n\)的棋盘,你和对手轮流在上面画"X" 当出现三个连续的X时,最后一步操作的人胜利 不难发现,在棋盘中画了一个X之后 问题等价于两个一样的子游戏 然后 ...

  9. WEB架构师成长之路 三

    Web架构师究竟都要学些什么?具备哪些能力呢?先网上查查架构师的大概的定义,参见架构师修炼之道这篇文章,写的还不错,再查查公司招聘Web架构师的要求. 总结起来大概有下面几点技能要求: 一. 架构师有 ...

  10. Linux服务器性能检测命令集锦

    uptime $ uptime 23:51:26 up 21:31, 1 user, load average: 30.02, 26.43, 19.02 这个命令可以快速查看机器的负载情况.在Linu ...