在一个类中,如果类没有指针成员,一切方便,因为默认合成的析构函数会自动处理所有的内存。但是如果一个类带了指针成员,那么需要我们自己来写一个析构函数来管理内存。在<<c++ primer>> 中写到,如果一个类需要我们自己写析构函数,那么这个类,也会需要我们自己写拷贝构造函数和拷贝赋值函数。

析构函数:

我们这里定义一个类HasPtr,这个类中包含一个int 类型的指针。然后定义一个析构函数,这个函数打印一句话。

HasPtr.h 类的头文件

 #pragma once
#ifndef __HASPTR__
#define __HASPTR__ class HasPtr
{
public:
HasPtr(int i,int *p);
//HasPtr& operator=(HasPtr&);
//HasPtr(const HasPtr&);
~HasPtr();
int get_ptr_value();
void set_ptr_value(int *p);
int get_val();
void set_val(int v);
private:
int val;
int *ptr;
}; #endif // !__HASPTR__

HasPtr.cpp 类的实现

 #include "stdafx.h"

 #include <iostream>
#include "HasPtr.h" using namespace std; HasPtr::HasPtr(int i, int *p)
{
val = i;
ptr = p;
} int HasPtr::get_ptr_value()
{
return *ptr;
} void HasPtr::set_ptr_value(int *p)
{
ptr = p;
} int HasPtr::get_val()
{
return val;
} void HasPtr::set_val(int v)
{
val = v;
} HasPtr::~HasPtr()
{
cout << "destructor of HasPtr " << endl;
}

ClassWithPointer 类,包含main入口,HasPtr在stack上。

 // ClassWithPointer.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <iostream>
#include "HasPtr.h"
using namespace std; int main()
{
int temp = ;
HasPtr ptr(,&temp);
cout << ptr.get_ptr_value() << endl;
cout << ptr.get_val() << endl;
system("PAUSE");
system("PAUSE");
return ;
}

执行该入口方法,发现最后还是打印了析构函数这句话,OK,在main 方法中,stack上定义了一个HasPtr,在main方法退出前,析构函数自动调用了。

如果将HasPtr改为动态对象,也就是放在堆上呢?

ClassWithPointer 类,包含main入口,HasPtr在heap上。

 // ClassWithPointer.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <iostream>
#include "HasPtr.h"
using namespace std; int main()
{
int temp = ;
//HasPtr ptr(2,&temp);
HasPtr *ptr = new HasPtr(,&temp);
cout << ptr->get_ptr_value() << endl;
cout << ptr->get_val() << endl;
system("PAUSE");
return ;
}

执行一下,发现析构函数没有调用。OK,我们在return 0前面添加一个delete ptr; 析构函数执行了。

所以,这里有两个结论:

  1. 当一个对象在stack 上时,析构函数自动调用。
  2. 当一个对象在heap上时,需要调用delete 语句,析构函数才会被执行。

现在在析构函数中调用delete 语句来删除指针成员。

头文件不变,HasPtr.cpp 文件代码如下:

 #include "stdafx.h"

 #include <iostream>
#include "HasPtr.h" using namespace std; HasPtr::HasPtr(int i, int *p)
{
val = i;
ptr = p;
} int HasPtr::get_ptr_value()
{
return *ptr;
} void HasPtr::set_ptr_value(int *p)
{
ptr = p;
} int HasPtr::get_val()
{
return val;
} void HasPtr::set_val(int v)
{
val = v;
} HasPtr::~HasPtr()
{
cout << "destructor of HasPtr " << endl;
delete ptr;
}

ClassWithPointer 代码如下:

 // ClassWithPointer.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <iostream>
#include "HasPtr.h"
using namespace std; int main()
{
int temp = ;
HasPtr ptr(,&temp);
cout << ptr.get_ptr_value() << endl;
cout << ptr.get_val() << endl;
system("PAUSE");
return ;
}

执行一下,正常打印结束后,抛出错误:

这里说明delete 不能删除stack 上的指针值。

现在在ClassWithPointer传入一个动态指针来测试一下。

 // ClassWithPointer.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <iostream>
#include "HasPtr.h"
using namespace std; int main()
{
int *temp = new int();
HasPtr ptr(,temp);
cout << ptr.get_ptr_value() << endl;
cout << ptr.get_val() << endl;
system("PAUSE");
return ;
}

执行后析构函数正常运行。所以这里有两个结论:

  1. delete 语句不能删除stack 上的指针值。
  2. delete 语句只能删除heap上的指针值,也就是new 出来的对象。

默认拷贝构造函数和默认赋值操作:

这里我们调用默认的构造函数和默认的赋值操作,看看会出现什么,为了方便查看,我在析构函数中打印了当前对象的地址,以及在main方法中打印了对象地址,这样就可以看到哪个对象调用了析构函数:

HasPtr.cpp 代码如下:

 #include "stdafx.h"

 #include <iostream>
#include "HasPtr.h" using namespace std; HasPtr::HasPtr(int i, int *p)
{
val = i;
ptr = p;
} int HasPtr::get_ptr_value()
{
return *ptr;
} void HasPtr::set_ptr_value(int *p)
{
ptr = p;
} int HasPtr::get_val()
{
return val;
} void HasPtr::set_val(int v)
{
val = v;
} HasPtr::~HasPtr()
{
cout << "destructor of HasPtr " << this << endl;
delete ptr;
}

ClassWithPointer 代码如下:

 // ClassWithPointer.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <iostream>
#include "HasPtr.h"
using namespace std; int main()
{
int *temp = new int();
HasPtr ptr(,temp);
cout << "ptr-------------->" << &ptr << endl;
cout << ptr.get_ptr_value() << endl;
cout << ptr.get_val() << endl; HasPtr ptr2(ptr);
cout << "ptr2-------------->" << &ptr2 << endl;
cout << ptr2.get_ptr_value() << endl;
cout << ptr2.get_val() << endl; HasPtr ptr3 = ptr;
cout << "ptr3-------------->" << &ptr3 << endl;
cout << ptr3.get_ptr_value() << endl;
cout << ptr3.get_val() << endl; system("PAUSE");
return ;
}

运行结果如下,最后还是报错了:

其实程序运行到第二个析构函数时,报错了。报错原因是,ptr 其实已经是pending指针了,因为这个ptr 指针所指向的地址已经被delete了。

不过我们这里最起码可以知道默认的拷贝构造函数和赋值操作,也是会直接复制指针值的,不是指针所指向的值。是指针变量的值,也就是地址。

所以这里引申出来的问题是:如何管理对象中指针成员的内存? 这个是一个核心问题。

上面的例子,就是默认的方式,但是管理失败了,因为析构函数到最后会删除pending 指针,导致异常发生。

智能指针:

引入一个类U_Ptr,用来管理我们需要在业务对象中需要的指针变量,假设为int *p。头文件如下:

 #pragma once
#ifndef __UPTR__
#define __UPTR__
#include "HasPtr.h"
#include <iostream> using namespace std;
class U_Ptr
{
friend class HasPtr;
int *ip;
size_t use; U_Ptr(int *p):ip(p),use() {}
~U_Ptr()
{
cout << "destruction:"<< *ip << endl;
delete ip;
}
};
#endif // !__UPTR__

现在我们的业务对象还是HasPtr。头文件如下:

 #pragma once
#ifndef __HASPTR__
#define __HASPTR__
#include "U_Ptr.h"
class HasPtr
{
public:
HasPtr(int *p, int i):ptr(new U_Ptr(p)),val(i){} HasPtr(const HasPtr &orgi) :ptr(orgi.ptr), val(orgi.val)
{
++ptr->use;
cout << "coming into copy construction:" << ptr->use << endl;
} HasPtr& operator=(const HasPtr &rhs); ~HasPtr(); int get_ptr_value() const;
int get_int() const;
void set_ptr(int *p);
void set_int(int i);
private:
U_Ptr *ptr;
int val;
}; #endif // !__HASPTR__

HasPtr.cpp 实现如下:

 #include "stdafx.h"
#include "HasPtr.h"
#include <iostream> using namespace std; HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr->use;
if (--ptr->use == )
{
delete ptr;
}
ptr = rhs.ptr;
val = rhs.val;
return *this;
} HasPtr::~HasPtr()
{
cout << "destruction:" << ptr->use << endl;
if (--ptr->use == )
{
delete ptr;
}
} int HasPtr::get_ptr_value() const
{
return *ptr->ip;
}
int HasPtr::get_int() const
{
return val;
}
void HasPtr::set_ptr(int *p)
{
ptr->ip = p;
}
void HasPtr::set_int(int i)
{
val = i;
}

测试类如下:

 // SmartPointer.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include "HasPtr.h"
#include <iostream> using namespace std; int main()
{
int *temp = new int();
HasPtr ptr(temp,);
cout << "ptr------------>" << endl;
cout << ptr.get_ptr_value() << endl;
cout << ptr.get_int() << endl;
HasPtr ptr2(ptr);
cout << "ptr2------------>" << endl;
cout << ptr2.get_ptr_value() << endl;
cout << ptr2.get_int() << endl;
system("PAUSE");
return ;
}

我们把U_Ptr 叫做智能指针,用于帮我们管理需要的指针成员。我们的业务对象HasPtr对象包含一个智能指针,这个指针在HasPtr 对象创建时创建,智能指针的use 变量用来记录业务对象HasPtr对象被复制了多少次,也就是说,有多少个相同的指针指向了ptr所指向的地方。如果要记录HasPtr对象一共有多少个一样的,那么就需要在拷贝构造函数和赋值操作处进行对use变量加一操作,在析构函数处进行减一操作。当减到0时,删除指针。

C++ 带有指针成员的类处理方式的更多相关文章

  1. 15.含有指针成员的类的拷贝[ClassCopyConstructorWithPointerMember]

    [题目] 下面是一个数组类的声明与实现.请分析这个类有什么问题,并针对存在的问题提出几种解决方案.  C++ Code  123456789101112131415161718192021222324 ...

  2. C++对象的复制——具有指针成员的类的对象的复制

    //smartvessel@gmail.com class Table{ Name * p; size_t sz; publish: Table(size_t s = 15){p = new Name ...

  3. C++ Primer 学习笔记_57_类和数据抽象 --管理指针成员

    复印控制 --管理指针成员 引言: 包括指针的类须要特别注意复制控制.原因是复制指针时.一个带指针成员的指针类 class HasPtr { public: HasPtr(int *p,int i): ...

  4. c/c++ 类成员变量,成员函数的存储方式,以及this指针在c++中的作用

    c/c++ 类成员变量,成员函数的存储方式,以及this指针在c++中的作用 c++不会像上图那样为每一个对象的成员变量和成员函数开辟内存空间, 而是像下图那样,只为每一个对象的成员变量开辟空间.成员 ...

  5. C++ Primer 有感(管理类的指针成员)

    C++类的指针成员与其他成员有所不同,指针成员指向一个内存地址,该地址的内存需要我没管理. 我现在分析一下为什么要管理指针成员. 有如下Student类,Student.h如下: [cpp] view ...

  6. !带有指针的类和struct赋值的本质 - host to device

    //这个变量必须在while循环外面 //原因是当将loadModels[modelNum].g_3DModel[0]赋值给新建类后 //里面的数值拷贝过去了,而里头的指针只给了地址 //所以如果这个 ...

  7. 【c++】类管理指针成员

    c++编程提倡使用标准库,一个原因是标准库大胆减少对指针的使用.但是许多程序是离不开指针的.包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不复制指针所指向的对象.这样当把一个 ...

  8. YTU 2636: B3 指向基类的指针访问派生类的成员函数

    2636: B3 指向基类的指针访问派生类的成员函数 时间限制: 1 Sec  内存限制: 128 MB 提交: 433  解决: 141 题目描述 领导类(Leader)和工程师类(Engineer ...

  9. C++ 类 & 对象-类成员函数-类访问修饰符-C++ 友元函数-构造函数 & 析构函数-C++ 拷贝构造函数

    C++ 类成员函数 成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义. 需要强调一点,在 :: 运算符之前必须使用类名.调用成员函数是在对象上使用点运算符(.),这样它就能操作与 ...

随机推荐

  1. Express 3.0新手指南入门教程

    在确认已经安装了node之后(下载), 在你的机器上创建一个目录,让我们来开始你的第一个应用程序吧 $ mkdir hello-world 在这个目录中你首先得定义一下你的应用程序“包”文件,它和其它 ...

  2. # C#不登录电脑启动程序

    我们知道开机自启动程序如果在用户不登录的情况下是不启动的,但是服务类程序是可以跨过用户登录启动的,例如IIS服务,SQL服务.如果我们已经写好了桌面应用程序,又希望他开机自启动,那就需要借助系统服务在 ...

  3. Ubuntu 16.04安装配置Samba服务

    Samba是开源软件,用来让Linux系统与Windows系统的SMB/CIFS网络协定做连结,实现Windows主机与Linux服务器之间的资源共享.Samba服务为两种不同的操作系统架起了一座桥梁 ...

  4. ArcGIS Pro 简明教程(4)工具和模型构建器

    ArcGIS Pro 简明教程(4)工具和模型构建器 by 李远祥 工具箱中的工具 ArcGIS Pro 在1.3版本基本上已经继承了ArcMap的所有工具,而且会不断加入一些它自身才有的工具,例如适 ...

  5. 计算机 计算机语言 常见的DOS命令

    你好 我是大福 你现在看的是大福笔记 我jie的女儿 8年级 在QQ上问我寒假作业 0度的水和0度的冰哪个更冷 什么? 0度水 0度冰这温度不是一样的么? 不可能 肯定没这么简单 她问的意思是不是手放 ...

  6. 体验 WebFont,网页上的艺术字

    在最新项目中,由于要频繁使用艺术字, 而用户设备没有此字体,因此以往的经验都是使用图片... 所以在同事的瞩目期许之下,我开始实验研究这个问题的解决方案1. 直接使用字体文件 @font-face { ...

  7. mysql 打开sql日志,记录所有sql

    我使用的mysql版本为:5.7.11 win7环境 记录下下载地址,省得每次百度搜了:http://dev.mysql.com/downloads/installer/ mysql 默认没有开启sq ...

  8. 【前端】:Dom

    前言: 昨天写了篇关于JavaScript的,今天写篇Dom的(插入4个实例),写完之后感觉知识点还不少~ 内容当然会用到[前端]:JavaScript的知识.下篇博客会写关于jQuery~~ 一.D ...

  9. 运行gpg --gen-key生成key时出现卡住的问题

    背景 在搭建Ubuntu16.04的本地apt源时,需要运行"gpg --gen-key"命令,但是在执行该命令时,出现了:"Not enough random byte ...

  10. vue路由表(简单)

    import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter) const first = { template: ...