15.含有指针成员的类的拷贝[ClassCopyConstructorWithPointerMember]
【题目】
下面是一个数组类的声明与实现。请分析这个类有什么问题,并针对存在的问题提出几种解决方案。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
template<typename T>
class Array { public: Array(), size(arraySize) { ) data = new T[size]; } ~Array() void setValue(unsigned index, const T &value) T getValue(unsigned index) const private: |
类中有指针变量,即深浅拷贝问题。因为类中没有实现拷贝构造函数和赋值构造函数,因此在用到拷贝构造函数和赋值构造函数的时候只会将指针简单复制,这样二个对象的这个指针会指向同一块内存,在对象删除及调用时会造成程序崩溃。
【解决方法】
【方案1】
将拷贝构造函数和赋值构造函数改成私有,这样程序调用时编译器会报错。
分析:我们注意在类的内部封装了用来存储数组数据的指针。软件存在的大部分问题通常都可以归结指针的不正确处理。
这个类只提供了一个构造函数,而没有定义构造拷贝函数和重载拷贝运算符函数。当这个类的用户按照下面的方式声明并实例化该类的一个实例
Array A(10);
Array B(A);
或者按照下面的方式把该类的一个实例赋值给另外一个实例
Array A(10);
Array B(10);
B=A;
编译器将调用其自动生成的构造拷贝函数或者拷贝运算符的重载函数。在编译器生成的缺省的构造拷贝函数和拷贝运算符的重载函数,对指针实行的是按位拷贝,仅仅只是拷贝指针的地址,而不会拷贝指针的内容。因此在执行完前面的代码之后,A.data和B.data指向的同一地址。当A或者B中任意一个结束其生命周期调用析构函数时,会删除data。由于他们的data指向的是同一个地方,两个实例的data都被删除了。但另外一个实例并不知道它的data已经被删除了,当企图再次用它的data的时候,程序就会不可避免地崩溃。
由于问题出现的根源是调用了编译器生成的缺省构造拷贝函数和拷贝运算符的重载函数。一个最简单的办法就是禁止使用这两个函数。于是我们可以把这两个函数声明为私有函数,如果类的用户企图调用这两个函数,将不能通过编译。实现的代码如下:
1
2 3 |
private:
Array(const Array ©); const Array &operator = (const Array ©); |
【方案2】
添加二函数。
最初的代码存在问题是因为不同实例的data指向的同一地址,删除一个实例的data会把另外一个实例的data也同时删除。因此我们还可以让构造拷贝函数或者拷贝运算符的重载函数拷贝的不只是地址,而是数据。由于我们重新存储了一份数据,这样一个实例删除的时候,对另外一个实例没有影响。这种思路我们称之为深度拷贝。实现的代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public:
Array(), size(copy.size) { ) { data = new T[size]; ; i < size; ++ i) setValue(i, copy.getValue(i)); } } const Array &operator = (const Array ©) if(data != NULL) size = copy.size; |
【方案3】
用引用计数器(指针)。
即在类中新增一变量用来记录类中指针被拷贝的次数。当调用默认构造函数时将次数置1,在调用以上二函数时将计数器+1,在析构函数中将计数器-1,当计数器值=0时,表示没有对象使用它,这时才真的删除指针指向的内存(此行为可能发生在赋值构造函数及析构函数中)。
为了防止有多个指针指向的数据被多次删除,我们还可以保存究竟有多少个指针指向该数据。只有当没有任何指针指向该数据的时候才可以被删除。这种思路通常被称之为引用计数技术。在构造函数中,引用计数初始化为1;每当把这个实例赋值给其他实例或者以参数传给其他实例的构造拷贝函数的时候,引用计数加1,因为这意味着又多了一个实例指向它的data;每次需要调用析构函数或者需要把data赋值为其他数据的时候,引用计数要减1,因为这意味着指向它的data的指针少了一个。当引用计数减少到0的时候,data已经没有任何实例指向它了,这个时候就可以安全地删除。实现的代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public:
Array(unsigned arraySize) : data(), size(arraySize), count(new unsigned int) { *count = ; ) data = new T[size]; } Array(const Array ©) ~Array() const Array &operator = (const Array ©) Release(); data = copy.data; private: delete count; unsigned int *count; |
【参考】
http://zhedahht.blog.163.com/blog/static/25411174200722710364233/
http://www.cnblogs.com/t427/archive/2012/08/10/2633133.html
http://blog.csdn.net/gamecreating/article/details/5382902
http://www.cppblog.com/xczhang/archive/2008/01/21/41569.html
15.含有指针成员的类的拷贝[ClassCopyConstructorWithPointerMember]的更多相关文章
- 关于Vector中的元素中含有指针成员的情况
对于容器,当容器的各个元素为类类型,且该类类型中含有指针成员时: 如果类类型的析构函数中包含了对指针变量指向内存的释放操作,则在利用clear()函数删除容器所有元素时,会自动调用类的析构函数,自动实 ...
- C++ 带有指针成员的类处理方式
在一个类中,如果类没有指针成员,一切方便,因为默认合成的析构函数会自动处理所有的内存.但是如果一个类带了指针成员,那么需要我们自己来写一个析构函数来管理内存.在<<c++ primer&g ...
- C++对象的复制——具有指针成员的类的对象的复制
//smartvessel@gmail.com class Table{ Name * p; size_t sz; publish: Table(size_t s = 15){p = new Name ...
- C++ Primer 学习笔记_57_类和数据抽象 --管理指针成员
复印控制 --管理指针成员 引言: 包括指针的类须要特别注意复制控制.原因是复制指针时.一个带指针成员的指针类 class HasPtr { public: HasPtr(int *p,int i): ...
- C++ 类 & 对象-类成员函数-类访问修饰符-C++ 友元函数-构造函数 & 析构函数-C++ 拷贝构造函数
C++ 类成员函数 成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义. 需要强调一点,在 :: 运算符之前必须使用类名.调用成员函数是在对象上使用点运算符(.),这样它就能操作与 ...
- C++ Primer 有感(管理类的指针成员)
C++类的指针成员与其他成员有所不同,指针成员指向一个内存地址,该地址的内存需要我没管理. 我现在分析一下为什么要管理指针成员. 有如下Student类,Student.h如下: [cpp] view ...
- 【C++ Primer 第15章】定义派生类拷贝构造函数、赋值运算符
学习资料 • 派生类的赋值运算符/赋值构造函数也必须处理它的基类成员的赋值 • C++ 基类构造函数带参数的继承方式及派生类的初始化 定义拷贝构造函数 [注意]对派生类进行拷贝构造时,如果想让基类的成 ...
- 【c++】类管理指针成员
c++编程提倡使用标准库,一个原因是标准库大胆减少对指针的使用.但是许多程序是离不开指针的.包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不复制指针所指向的对象.这样当把一个 ...
- YTU 2636: B3 指向基类的指针访问派生类的成员函数
2636: B3 指向基类的指针访问派生类的成员函数 时间限制: 1 Sec 内存限制: 128 MB 提交: 433 解决: 141 题目描述 领导类(Leader)和工程师类(Engineer ...
随机推荐
- webform中的验证控件及两个应用技巧
一.非空验证--RequiredFiledValidator <一>属性: ErrorMessage--验证出错后的提示信息 ControlToValidate--要验证的控件的ID Di ...
- form表单提交和ajax提交的区别
form表单是整个页面跳到服务器的地址然后提交数据: ajax是往这个地址post数据 <form style="padding:0px;margin:0px;" targe ...
- AU3学习资源
AU3中文站:http://www.autoitx.com/
- 洛谷P1755 斐波那契的拆分
题目背景 无 题目描述 已知任意一个正整数都可以拆分为若干个斐波纳契数,现在,让你求出n的拆分方法 输入输出格式 输入格式: 一个数t,表示有t组数据 接下来t行,每行一个数n(如题) 输出格式: t ...
- [IOS Delegate和协议]
转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/25655443 作者:小马 代理和协议的语法这里不赘述,自己查资料. 这个demo的 ...
- urllib2.URLError: <urlopen error [Errno 10061] >
今天来运行以前的python脚本,结果报这个错:urllib2.URLError: <urlopen error [Errno 10061] > 原来是因为 解决方法:打开IE浏览器,依次 ...
- SCI完全攻略:从构思到发表
- c/s架构nginx+php-fpm通信原理
FastCGI是一个运用于Http Server和动态脚本语言间通信的接口,多数流行的Http Server都支持FastCGI,包括Apache.Nginx和lighttpd等.同时,Fas ...
- JavaScript实现全排列
<html xmlns="http://www.w3.org/1999/xhtml"> <head> </head> <body> ...
- 使用ymPrompt弹框
使用弹框 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8&q ...