含有指针成员的类的拷贝
2014-09-04 11:15
232 查看
题目:下面是一个数组类的声明与实现。请分析这个类有什么问题,并针对存在的问题提出几种解决方案。
template<typename T>
class Array
{
public:
Array(unsignedarraySize):data(0), size(arraySize)
{
if(size> 0)
data = newT[size];
}
~Array()
{
if(data)delete[] data;
}
voidsetValue(unsigned index, const T& value)
{
if(index< size)
data[index] = value;
}
T getValue(unsignedindex) const
{
if(index< size)
returndata[index];
else
returnT();
}
private:
T* data;
unsignedsize;
};
分析:这个类中有指针成员变量,在这种情况下,如果处理不当,很容易造成内存泄露问题。
该类没有提供拷贝构造函数和赋值(=)运算符的重载函数,当需要调用拷贝构造函数或者赋值操作时,编译器会自动调用系统生成的默认拷贝构造函数或者默认赋值运算符重载函数,而这两个默认的函数是以逐个拷贝成员变量的方式来赋值数据成员,在这种拷贝方式下,赋值指针被定义为将一个变量的地址赋给另一个变量,也即两个指针指向同一个地址,这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的(两次释放同一块内存是错误的做法,而且很可能造成堆的奔溃)。
所以,如果按如下方式调用就会出现内存泄露问题:
Array<int> A(20);
Array<int> B(A);
或者
Array<int> A(20);
Array<int> B(10);
A = B;
编译器会自动调用默认拷贝构造函数和默认赋值运算符重载函数,而这两个默认的函数对指针都是按位拷贝,只拷贝地址,而不会把指针所指向的内容做拷贝,因此当A.data和B.data指向同一位置,当A或者B中任意一个结束生命周期时,就会delete data,由于A和B指向的是同一位置,所以两个实例的data都被delete了,所以会出现两次释放同一片内存空间的问题,这样的做法是错误的。
解决方案1: 禁用拷贝构造函数和赋值运算符重载函数
private:
Array(const Array& array);
const Array& operator=(const Array& array);
解决方案2:添加拷贝构造函数和赋值运算符重载函数,并且合理的实现。
public:
Array(const Array& array);
const Array& operator=(const Array& array);
template<typename T>
Array<T>::Array(const Array&array): data(0), size(array.size)
{
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; i++)
setValue(i, array.getValue(i));
}
}
template<typename T>
const Array<T>& Array<T>::operator=(const Array<T>& array)
{
if(this == &array)
return *this;
if(data != NULL)
{
delete[] data;
data = NULL;
}
size = array.size;
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; i++)
setValue(i, array.getValue(i));
}
}
解决方案3:用引用计数来管理指针成员
public:
Array(const Array& array);
const Array& operator=(const Array& array);
template<typename T>
Array<T>::Array(unsignedarraySize):data(0), size(arraySize)
{
count = 1;
if(size> 0)
data = newT[size];
}
template<typename T>
Array<T>::Array(const Array&array): data(0), size(array.size), count(array.count)
{
++count;
}
template<typename T>
const Array<T>& Array<T>::operator=(const Array<T>& array)
{
if(data == array.data)
return *this;
release();
data = array.data;
size = array.szie;
count = array.count;
++count;
}
template<typename T>
Array<T>::~Array()
{
release();
}
private:
void release()
{
--count;
if(count == 0)
{
if(data)
{
delete[] data;
data = NULL;
}
}
}
因此,如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符。
template<typename T>
class Array
{
public:
Array(unsignedarraySize):data(0), size(arraySize)
{
if(size> 0)
data = newT[size];
}
~Array()
{
if(data)delete[] data;
}
voidsetValue(unsigned index, const T& value)
{
if(index< size)
data[index] = value;
}
T getValue(unsignedindex) const
{
if(index< size)
returndata[index];
else
returnT();
}
private:
T* data;
unsignedsize;
};
分析:这个类中有指针成员变量,在这种情况下,如果处理不当,很容易造成内存泄露问题。
该类没有提供拷贝构造函数和赋值(=)运算符的重载函数,当需要调用拷贝构造函数或者赋值操作时,编译器会自动调用系统生成的默认拷贝构造函数或者默认赋值运算符重载函数,而这两个默认的函数是以逐个拷贝成员变量的方式来赋值数据成员,在这种拷贝方式下,赋值指针被定义为将一个变量的地址赋给另一个变量,也即两个指针指向同一个地址,这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的(两次释放同一块内存是错误的做法,而且很可能造成堆的奔溃)。
所以,如果按如下方式调用就会出现内存泄露问题:
Array<int> A(20);
Array<int> B(A);
或者
Array<int> A(20);
Array<int> B(10);
A = B;
编译器会自动调用默认拷贝构造函数和默认赋值运算符重载函数,而这两个默认的函数对指针都是按位拷贝,只拷贝地址,而不会把指针所指向的内容做拷贝,因此当A.data和B.data指向同一位置,当A或者B中任意一个结束生命周期时,就会delete data,由于A和B指向的是同一位置,所以两个实例的data都被delete了,所以会出现两次释放同一片内存空间的问题,这样的做法是错误的。
解决方案1: 禁用拷贝构造函数和赋值运算符重载函数
private:
Array(const Array& array);
const Array& operator=(const Array& array);
解决方案2:添加拷贝构造函数和赋值运算符重载函数,并且合理的实现。
public:
Array(const Array& array);
const Array& operator=(const Array& array);
template<typename T>
Array<T>::Array(const Array&array): data(0), size(array.size)
{
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; i++)
setValue(i, array.getValue(i));
}
}
template<typename T>
const Array<T>& Array<T>::operator=(const Array<T>& array)
{
if(this == &array)
return *this;
if(data != NULL)
{
delete[] data;
data = NULL;
}
size = array.size;
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; i++)
setValue(i, array.getValue(i));
}
}
解决方案3:用引用计数来管理指针成员
public:
Array(const Array& array);
const Array& operator=(const Array& array);
template<typename T>
Array<T>::Array(unsignedarraySize):data(0), size(arraySize)
{
count = 1;
if(size> 0)
data = newT[size];
}
template<typename T>
Array<T>::Array(const Array&array): data(0), size(array.size), count(array.count)
{
++count;
}
template<typename T>
const Array<T>& Array<T>::operator=(const Array<T>& array)
{
if(data == array.data)
return *this;
release();
data = array.data;
size = array.szie;
count = array.count;
++count;
}
template<typename T>
Array<T>::~Array()
{
release();
}
private:
void release()
{
--count;
if(count == 0)
{
if(data)
{
delete[] data;
data = NULL;
}
}
}
因此,如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符。
相关文章推荐
- 含有指针成员的类的拷贝
- 【转帖】含有指针成员的类的拷贝
- 每天一道算法题15 含有指针成员的类的拷贝
- 含有指针成员的类的拷贝
- 数据结构练习(14)含有指针成员的类的拷贝
- 程序员面试题精选100题(15)-含有指针成员的类的拷贝[C/C++/C#]
- 程序员面试题精选100题(15)-含有指针成员的类的拷贝[C/C++/C#]
- 15.含有指针成员的类的拷贝[ClassCopyConstructorWithPointerMember]
- 程序员面试题精选100题(15)-含有指针成员的类的拷贝
- 15 -含有指针成员的类的拷贝
- 含有指针成员的类的拷贝[C/C++/C#]
- 程序员面试题精选100题(15)-含有指针成员的类的拷贝
- 含有指针成员的类的拷贝
- 程序员面试题精选(15):含有指针成员的类的拷贝
- 程序员面试100题之十五 和 三十, 含有指针成员的类的拷贝(异常安全的赋值运算符重载)
- 含有指针成员的类的拷贝
- 程序员面试题精选100题(15)-含有指针成员的类的拷贝
- 【100题】含有指针成员的类的拷贝问题~~
- 每天一道算法题15 含有指针成员的类的拷贝
- 含有指针成员的类的拷贝