程序员面试题精选100题(15)-含有指针成员的类的拷贝[C/C++/C#]

字号:|
2019-09-22    FW.5VV.CN范文网

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

template<typename T> class Array
{
public:
      Array(unsigned arraySize):data(0), size(arraySize)
      {
            if(size > 0)
                  data = new T[size];
      }

      ~Array()
      {
            if(data) delete[] data;
      }

      void setValue(unsigned index, const T& value)
      {
            if(index < size)
                  data[index] = value;
      }

      T getValue(unsigned index) const
      {
            if(index < size)
                  return data[index];
            else
                  return T();
      }

private:
      T* data;
      unsigned size;
};

分析:我们注意在类的内部封装了用来存储数组数据的指针。软件存在的大部分问题通常都可以归结指针的不正确处理。

这个类只提供了一个构造函数,而没有定义构造拷贝函数和重载拷贝运算符函数。当这个类的用户按照下面的方式声明并实例化该类的一个实例

Array A(10);
Array B(A);

或者按照下面的方式把该类的一个实例赋值给另外一个实例

Array A(10);
Array B(10);
B=A;

编译器将调用其自动生成的构造拷贝函数或者拷贝运算符的重载函数。在编译器生成的缺省的构造拷贝函数和拷贝运算符的重载函数,对指针实行的是按位拷贝,仅仅只是拷贝指针的地址,而不会拷贝指针的内容。因此在执行完前面的代码之后,A.dataB.data指向的同一地址。当A或者B中任意一个结束其生命周期调用析构函数时,会删除data。由于他们的data指向的是同一个地方,两个实例的data都被删除了。但另外一个实例并不知道它的data已经被删除了,当企图再次用它的data的时候,程序就会不可避免地崩溃。

由于问题出现的根源是调用了编译器生成的缺省构造拷贝函数和拷贝运算符的重载函数。一个最简单的办法就是禁止使用这两个函数。于是我们可以把这两个函数声明为私有函数,如果类的用户企图调用这两个函数,将不能通过编译。实现的代码如下:

private:
      Array(const Array& copy);
      const Array& operator = (const Array& copy);

最初的代码存在问题是因为不同实例的data指向的同一地址,删除一个实例的data会把另外一个实例的data也同时删除。因此我们还可以让构造拷贝函数或者拷贝运算符的重载函数拷贝的不只是地址,而是数据。由于我们重新存储了一份数据,这样一个实例删除的时候,对另外一个实例没有影响。这种思路我们称之为深度拷贝。实现的代码如下:

public:
      Array(const Array& copy):data(0), size(copy.size)
      {
            if(size > 0)
            {
                  data = new T[size];
                  for(int i = 0; i < size; ++ i)
                        setValue(i, copy.getValue(i));
            }
      }

      const Array& operator = (const Array& copy)