惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
D
Docker
有赞技术团队
有赞技术团队
D
DataBreaches.Net
The GitHub Blog
The GitHub Blog
爱范儿
爱范儿
H
Help Net Security
美团技术团队
MyScale Blog
MyScale Blog
B
Blog RSS Feed
C
Check Point Blog
Microsoft Security Blog
Microsoft Security Blog
阮一峰的网络日志
阮一峰的网络日志
A
About on SuperTechFans
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
GbyAI
GbyAI
G
Google Developers Blog
月光博客
月光博客
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Blog — PlanetScale
Blog — PlanetScale
MongoDB | Blog
MongoDB | Blog
F
Fortinet All Blogs

博客园 - BAsil

ubuntu下安装git server以及eclipse egit插件配置 windows下调试android源代码 Mac OS 10.6(Snow Leopard)编译Android源代码 使用Mono Runtime Bundle制作安装包让C#桌面应用程序脱离net framework C++指针存储结构 重构一个贪吃蛇游戏(linux c++) Ubuntu 9.10 Server 配置Apache + WebDav 实现文档协同管理 javascript事件机制与jQuery.bind的补充说明 javascript 事件机制 与 jQuery.Bind Ubuntu 9.10 Server 配置基于SSL的Subversion Ubuntu 9.10 Server (Karmic) 迁移Bugzilla 域环境下配置连接sql server 的asp.net网站 注意IIS 7.0的集成管道模式下HttpApplication 请求管道事件的问题 出现Unable to launch Visual Studio's Localhost Web Server问题解决 基于TCP的网络游戏黑白棋系列(四):游戏棋盘 基于TCP的网络游戏黑白棋系列(三):游戏大厅 基于TCP的网络游戏黑白棋系列(二):数据传输 基于TCP的网络游戏黑白棋系列(一):建立连接 基于TCP的网络游戏黑白棋系列教程开篇
c++对象成员函数返回自身引用时出现的诡异问题及解决办法
BAsil · 2010-10-14 · via 博客园 - BAsil

2010-10-14 14:05  BAsil  阅读(3329)  评论()    收藏  举报

list.h (实际上是数据结构顺序表的一个例子,为了展现问题,我简化了部分代码,只留下插入和打印)
#include<iostream>
using namespace std;
class List
{
public:
    List(int max_list_size)
    {
      max_size=max_list_size;
      data=new int[max_size];
      n=0;
    }
    ~List()
    {
        delete[]data;
    }
    bool empty()//const
    {
        return n==0;
    }
    int size()
    {
        return n;
    }
    List insert(int k,int &x)
    {
        for(int i=n-1;i>=k;i--)
            data[i+1]=data[i];
        data[k]=x;
        //此处有值
        cout<<"data inserted phase"<<endl;
        cout<<"data[0]="<<data[0]<<endl;
        n++;
        return *this;
    }
    void print_list() //const;
    {
      //此处没值
      cout<<"print phase"<<endl;
      for(int i=0;i<n;i++)
      {
        cout<<"data["<<i<<"]="<<data[i]<<endl;
      }
    }

private:
    int n;
    int max_size;
    int *data;
};

main函数

#include<iostream>
using namespace std;
#include"List.h"
int main()
{

    List list(5);
    int t;
    cout<<"please enter a integer and press enter"<<endl;
    cin>>t;
    cout<<t<<endl;
    list.insert(0,t);
    list.print_list();
    system("pause");
    return 0;
}

运行时输入2 回车 结果如图

result

你能看出问题在哪里吗?

当我把成员函数insert的返回值类型 由 List 改为List & 结果就正确了

result1

下面我们分析下出现该问题的原因,以及应对方法

首先大家要了解的是函数的返回值的传递方式,这里大家应该牢记的是函数返回值的传递和函数传递参数的方式是基本一样的,关于函数传递参数的方式请参见C++指针存储结构

函数返回值的传递也分为按值传递和按引用传递两种

示例代码中成员函数insert的函数原型为List insert(int k,int &x) 为按值传递,所以在返回值实际上复制了一个新的对象x,那么这个新的对象和原有的调用该函数的对象(假设为l)有什么区别和联系?

要回答这个问题就要了解一下拷贝构造函数,拷贝构造函数会在赋值(=)以及函数按值传递参数或者按值传递返回值的时候调用;当类中没有拷贝构造函数时,会调用默认的拷贝构造函数。一般情况下,默认构造函数会对源对象的每个数据成员一一赋值给目标对象的同一数据成员,但如果数据成员包含指针,可能导致严重问题。

List list(5);
int t;
cin>>t;    
list.insert(0,t);

当调用list.insert方法时,由于返回值是按值传递的,所以会调用默认拷贝构造函数复制一个新的对象(假设为x),但由于List类数据成员包含指针,会出现如下情况

ref1

目前好像看不出什么问题,接下来有问题了,由于该成员函数的返回对象x(实际上是临时对象)并没有赋给任何变量,所以会立即调用析构函数析构掉,析构函数的代码

~List()
    {
        delete[]data;
    }

析构以后如图

ref2

此时对象l的data指针却还指向那块被释放掉了的区域,形成了“虚悬引用”,也就是俗称的“野指针”。所以也当查看对象l的data[0]是变成一个奇怪的值。

问题的解决是加上自己的拷贝构造函数

List(const List& right):n(right.n),max_size(right.max_size)
{
        data = new int [max_size];
        for (int i=0; i<n;i++)
        {
            data[i]=right.data[i];
        }
}

此时调用 l.insert时,内存存储示意图如下

ref3

由于x是临时对象会接着调用析构函数

ref4

增加了拷贝构造函数只是能够让结果正确的显示,但是在实际开发中,很少采用函数按值返回的形式返回一个对象(相当于赋值一份),浪费了空间不说,还容易造成上述的问题,解决的办法是按引用返回,做如下修改

List&  insert(int k,int &x)
    {
        for(int i=n-1;i>=k;i--)
            data[i+1]=data[i];
        data[k]=x;
        cout<<"data inserted phase"<<endl;
        cout<<"data[0]="<<data[0]<<endl;
        n++;
        return *this;
    }

把List改为List& ,引用就相当于一个别名(我的理解是隔山打牛,参见C++指针存储结构),至此问题解决。