C++中new的用法及显示调用析构函数

C++中new的⽤法及显⽰调⽤析构函数
最近被问到了C++内存池的问题,其中不免涉及到在指定内存地址调⽤对象构造函数以及显⽰调⽤对象析构函数的情况。
C++中new的⽤法
new是C++中⽤于分配的,在C语⾔中⼀般使⽤。
(1)plain new顾名思义就是普通的new,就是我们惯常使⽤的new。分配内存,调⽤构造函数,在C++中是这样定义的:
1void* operator new(std::size_t) throw(std::bad_alloc);
2void operator delete(void *) throw();
plain new在分配失败的情况下,抛出异常std::bad_alloc⽽不是返回NULL,因此通过判断返回值是否为NULL是徒劳的。
1 #include "stdafx.h"
2 #include <iostream>
3using namespace std;
4char *GetMemory(unsigned long size)
5 {
6char *p=new char[size];//分配失败,不是返回NULL
7return p;
8 }
9
10int main()
11 {
12try
星贝云链
13    {
14char *p=GetMemory(10e11);// 分配失败抛出异常std::bad_alloc
15//...........
16if(!p)//徒劳
17            cout<<"failure"<<endl;
18delete [] p;
19    }
20catch(const std::bad_alloc &ex)
21    {
22          cout<<ex.what()<<endl;
23    }
24
25return0;
26 }
(2)nothrow new是不抛出异常的运算符new的形式。nothrow new在失败时,返回NULL。定义如下:
1void * operator new(std::size_t,const std::nothrow_t&) throw();
2void operator delete(void*) throw(); 
1 #include "stdafx.h"
2 #include <iostream>威海地税
3 #include <new>
4using namespace std;
5char *GetMemory(unsigned long size)
6 {
7char *p=new(nothrow) char[size];//分配失败,是返回NULL
8if(NULL==p)
9          cout<<"alloc failure!"<<endl;
10return p;
11 }
12
13int main()
14 {
15try
16    {
任弼时中学
17char *p=GetMemory(10e11);
18//...........
19if(p==NULL)
20                cout<<"failure"<<endl;
21delete [] p;
22    }
23catch(const std::bad_alloc &ex)
24    {
25          cout<<ex.what()<<endl;
26    }
27return0;
28 }
(3)placement new意即“放置”,这种new允许在⼀块已经分配成功的内存上重新构造对象或对象数组。placement new不⽤担⼼内存分配
失败,因为它根本不分配内存,它做的唯⼀⼀件事情就是调⽤对象的构造函数。定义如下:
1void* operator new(size_t,void*);
2void operator delete(void*,void*);
palcement new的主要⽤途就是反复使⽤⼀块较⼤的动态分配的内存来构造不同类型的对象或者他们的数组。placement new构造起来的对象或其数组,要显⽰的调⽤他们的析构函数来销毁,千万不要使⽤delete。
1 #include "stdafx.h"
2 #include <iostream>
3 #include <new>
4using namespace std;
5class ADT
6 {
7int i;
8int j;
9public:
10    ADT()
11    {
12    }
13    ~ADT()
14    {
15    }
16 };
17
18int main()
19 {
20char *p=new(nothrow) char[sizeof(ADT)+2];
21if(p==NULL)
22          cout<<"failure"<<endl;
23    ADT *q=new(p) ADT;  //placement new:不必担⼼失败
24// delete q;//错误!不能在此处调⽤delete q;
25    q->ADT::~ADT();//显⽰调⽤析构函数
26delete []p;
27return0;
28 }
使⽤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[34];
int *b = new int[];
int (*c)[2] = new
int[34][2];
int (*d)[2] = new int[][2];
int (*e)[2][3] = new int[34][2][3];
int (*f)[2][3] = new int[][2][3];
a[0] = 1;
b[0] = 1; //运⾏时错误,⽆分配的内存,b只起指针的作⽤,⽤来指向相应的数据
c[0][0] = 1;
d[0][0] = 1;//运⾏时错误,⽆分配的内存,d只起指针的作⽤,⽤来指向相应的数据
e[0][0][0] = 1;
f[0][0][0] = 1;//运⾏时错误,⽆分配的内存,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 (*)[2]
int (*)[2]
int (*)[2][3]
int (*)[2][3]
深⼊学习⽂献:
C++显⽰调⽤析构函数
⼀、⽂章来由
现在在写⼀个项⽬,需要⽤到多叉树存储结构,但是在某个时候,我需要销毁这棵树,这意味着如果我新建了⼀个树对象,我很可能在某处希望将这个对象的声明周期终结,⾃然会想到显⽰调⽤析构函数,但是就扯出来这么⼤个陷阱。
⼆、原因
在了解为什么不要轻易显⽰调⽤析构函数之前,先来看看预备知识。
dna探针
为了理解这个问题,我们必须⾸先弄明⽩“堆”和“栈”的概念。
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
{
财税201170号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;
}
五、⼩结
所以,⼀般不要⾃作聪明的去显⽰调⽤析构函数。

本文发布于:2024-09-23 00:39:35,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/434995.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:对象   函数   内存   释放   分配
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议