新秀系列C/C++经典问题(六)

类包含一个指向成员复制

称号:下面是类和执行的阵列的声明。题。并针对存在的问题提出几种解决方式。

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.data和B.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)
    {
        if (this == &copy) // 自赋值检查
            return *this;

        if (data != NULL)
        {
            delete[]data;
            data = NULL;
        }

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

为了防止有多个指针指向的数据被多次删除,我们还能够保存到底有多少个指针指向该数据。仅仅有当没有不论什么指针指向该数据的时候才干够被删除。这样的思路通常被称之为引用计数技术。在构造函数中,引用计数初始化为1;每当把这个实例赋值给其它实例或者以參数传给其它实例的构造拷贝函数的时候。引用计数加1,由于这意味着又多了一个实例指向它的data;每次须要调用析构函数或者须要把data赋值为其它数据的时候,引用计数要减1,由于这意味着指向它的data的指针少了一个。

当引用计数降低到0的时候,data已经没有不论什么实例指向它了,这个时候就能够安全地删除。

实现的代码例如以下:

public:
    Array(unsigned arraySize)
        :data(0), size(arraySize), count(new unsigned int)
    {
        *count = 1;
        if (size > 0)
            data = new T[size];
    }

    Array(const Array& copy)
        : size(copy.size), data(copy.data), count(copy.count)
    {
        ++(*count);
    }

    ~Array()
    {
        Release();
    }

    const Array& operator = (const Array& copy)
    {
        if (data == copy.data)
            return *this;

        Release();

        data = copy.data;
        size = copy.size;
        count = copy.count;
        ++(*count);
    }

 private:
     void Release()
     {
         --(*count);
         if (*count == 0)
         {
             if (data)
             {
                 delete[]data;
                 data = NULL;
             }

             delete count;
             count = 0;
         }
     }

     unsigned int *count;

请指出错误, 转载请注明出处, 谢谢!

版权声明:本文博主原创文章。博客,未经同意不得转载。

时间: 2024-07-28 14:10:36

新秀系列C/C++经典问题(六)的相关文章

算法入门经典第六章 例题6-14 Abbott的复仇(Abbott&#39;s Revenge)BFS算法实现

Sample Input 3 1 N 3 3 1 1 WL NR * 1 2 WLF NR ER * 1 3 NL ER * 2 1 SL WR NF * 2 2 SL WF ELF * 2 3 SFR EL * 0 Sample Output (3,1) (2,1) (1,1) (1,2) (2,2) (2,3) (1,3) (1,2) (1,1) (2,1) (2,2) (1,2) (1,3) (2,3) (3,3) 析 题目的大意是,输入起点,离开起点时的朝向和终点,求一条最短路. 每一个

Linux Shell系列教程之(十六) Shell输入输出重定向

本文是Linux Shell系列教程的第(十六)篇,更多Linux Shell教程请看:Linux Shell系列教程 Shell中的输出和输入的重定向是在使用中经常用到的一个功能,非常实用,今天就为大家介绍下Shell输入输出重定向的相关知识. 一.Shell输入输出重定向概述 在了解重定向相关知识之前,我们先来看看Linux的文件描述符. Linux的文件描述符可以理解为linux跟踪打开文件,而分配的一个数字,这个数字有点类似c语言操作文件时候的句柄,通过句柄就可以实现文件的读写操作. 用

菜鸟系列之C/C++经典试题(六)

含有指针成员的类的拷贝 题目:下面是一个数组类的声明与实现.请分析这个类有什么问题,并针对存在的问题提出几种解决方案. 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(u

码农的奋斗之路 穷爸爸富爸爸系列-致富需要做的六件事 读后感

很多人在财务上挣扎,他们只知道为钱而工作,但却从未想过如何让钱为自己工作.每个人都该思考的问题!!! 第一件事  做好为财务自由付出努力的准备 安稳的代价 省吃俭用的代价 这两方面都不做.其他途径---财商! 第二件事 不要怕犯错,但要善于从错误中吸取经验 从错误中学习,敢于试错,前提是你迈出第一步出去,即便迈出后错了,人生很长,错不可怕,要学会从错误中吸取经验. 第三件事 按信息时代的法则对自己进行教育 基本的金融教育 现金流 一般所得 证券投资组合 被动收入 职业收入 你的成功取决于有多少答

[Asp.net 开发系列之SignalR篇]专题六:使用SignalR实现消息提醒

一.引言 前面一篇文章我介绍了如何使用SignalR实现图片的传输,然后对于即时通讯应用来说,消息提醒是必不可少的.现在很多网站的都有新消息的提醒功能.自然对于SignalR系列也少不了这个功能的实现了.在这篇文章中将介绍如何使用SignalR+iNotify库来实现新消息的声音和弹框提醒. 二.消息提醒的实现思路 消息提醒也就是当客户有新消息来时,在客户端的右下角进行弹框提醒.要实现这个功能的思路是: SignalR服务端推送消息到客户端的实现方式为调用客户端的receiveMessage方法

矩阵经典题目六:poj 3070 Fibonacci

http://poj.org/problem?id=3070 按已构造好的矩阵,那么该矩阵的n次方的右上角的数便是f[n]. #include <stdio.h> #include <iostream> #include <map> #include <set> #include <list> #include <stack> #include <vector> #include <math.h> #inclu

算法入门经典第六章 例题6-5 移动盒子

例题 6-5 移动盒子(Boxes in a Line, UVa127675) 问题 给定一行盒子,从左到右编号依次为1,2,...,n.可以执行以下命令: 1 X Y 把盒子 X 移动到 Y 的左边(如果已经在左边,忽略此命令) 2 X Y 把盒子 X 移动到 Y 右边(如果X已经在Y的右边,忽略此命令) 3 X Y交换 X 和 Y 的位置 4 把整个顺序颠倒 指令保证合法,即X 不等于 Y, 输入包含不超过10组数据,每组第一行为盒子的数目n和指令的数目m(1<=n,m<=100000).

U3D架构系列之- FSM有限状态机设计六(总结篇)

由于最近一直赶项目进度,没时间写,昨晚终于项目终于完成了,空闲下来,做一个总结.在这一篇中主要是把前五章一起总结一下,以及通过举例演示如何使用?有限状态机在游戏中使用的地方非常多,比如我们界面之间的切换,角色的状态切换等等.所以非常值得大家去学习一下,接下来我们主要实现的功能,为了表达清楚,我通过图例给大家说明一下: 给大家解析一下,程序运行首先进入主菜单,里面有三个按钮,开始游戏,音量,退出游戏.先从第一个说起,如果是开始游戏,它会进入到下一个界面游戏界面,游戏界面有个返回主菜单功能.二者可以

C++系列: 如何将十六机制的字符串转成整数

bool convertHexStringToInt(char* pstrHex, unsigned long* pResult) { if(sscanf(pstrHex, “%x”, pResult) == 0) return false; else return true; }