加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

关于向std::vector中插入自定义对象的一些问题

(2017-03-08 09:12:39)
标签:

vector

push_back

拷贝构造函数

分类: C_Plus_Plus
昨天在一个技术群中,看到别人贴了这么一段代码,主要操作就是将自定义的对象 Point 插入到 vector 中,但运行时出现了错误:

class Point
{
public:
    Point();
    ~Point();
    void setPoint(int x, int y); //在类内对成员函数进行声明
    void printPoint();

private:
    int xPos;
    int yPos;
    int *p;
};
 
Point::Point()
{
    new int(100);
}

Point::~Point()
{
    if(p)
        delete p;
}
 
void Point::setPoint(int x, int y)
{
    xPos x;
    yPos y;
}

void Point::printPoint()
{
    cout<< "x << xPos;
    cout<< << yPos << endl;
}

int main()  
    
    vector ivec;     
    for(vector::size_type ix 0;ix != 10;++ix)
    
        Point M;
        M.setPoint(ix+1, ix+1);
        ivec.push_back(M);   
        M.printPoint();
      
    return 0;  
}

运行结果如下:

1
2
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x095be008 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(+0x6b591)[0x9b0591]
/lib/tls/i686/cmov/libc.so.6(+0x6cde8)[0x9b1de8]
/lib/tls/i686/cmov/libc.so.6(cfree+0x6d)[0x9b4ecd]
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x376741]
./a.out[0x80489ea]
./a.out[0x80495ac]
./a.out[0x804947e]
./a.out[0x80492af]
./a.out[0x8048ee8]
./a.out[0x8049123]
./a.out[0x8048d9b]
./a.out[0x8048af3]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0x95bbd6]
./a.out[0x80488d1]
======= Memory map: ========
00110000-00134000 r-xp 00000000 08:02 394138     /lib/tls/i686/cmov/libm-2.11.1.so
00134000-00135000 r--p 00023000 08:02 394138     /lib/tls/i686/cmov/libm-2.11.1.so
00135000-00136000 rw-p 00024000 08:02 394138     /lib/tls/i686/cmov/libm-2.11.1.so
00189000-001a4000 r-xp 00000000 08:02 394104     /lib/ld-2.11.1.so
001a4000-001a5000 r--p 0001a000 08:02 394104     /lib/ld-2.11.1.so
001a5000-001a6000 rw-p 0001b000 08:02 394104     /lib/ld-2.11.1.so
002bb000-003a4000 r-xp 00000000 08:02 274864     /usr/lib/libstdc++.so.6.0.13
003a4000-003a5000 ---p 000e9000 08:02 274864     /usr/lib/libstdc++.so.6.0.13
003a5000-003a9000 r--p 000e9000 08:02 274864     /usr/lib/libstdc++.so.6.0.13
003a9000-003aa000 rw-p 000ed000 08:02 274864     /usr/lib/libstdc++.so.6.0.13
003aa000-003b1000 rw-p 00000000 00:00 
00945000-00a98000 r-xp 00000000 08:02 394174     /lib/tls/i686/cmov/libc-2.11.1.so
00a98000-00a99000 ---p 00153000 08:02 394174     /lib/tls/i686/cmov/libc-2.11.1.so
00a99000-00a9b000 r--p 00153000 08:02 394174     /lib/tls/i686/cmov/libc-2.11.1.so
00a9b000-00a9c000 rw-p 00155000 08:02 394174     /lib/tls/i686/cmov/libc-2.11.1.so
00a9c000-00a9f000 rw-p 00000000 00:00 
00d8c000-00da9000 r-xp 00000000 08:02 394730     /lib/libgcc_s.so.1
00da9000-00daa000 r--p 0001c000 08:02 394730     /lib/libgcc_s.so.1
00daa000-00dab000 rw-p 0001d000 08:02 394730     /lib/libgcc_s.so.1
00e1b000-00e1c000 r-xp 00000000 00:00          [vdso]
08048000-0804b000 r-xp 00000000 08:41 661340     /media/openwrt/lds/test/a.out
0804b000-0804c000 r--p 00002000 08:41 661340     /media/openwrt/lds/test/a.out
0804c000-0804d000 rw-p 00003000 08:41 661340     /media/openwrt/lds/test/a.out
095be000-095df000 rw-p 00000000 00:00          [heap]
b7600000-b7621000 rw-p 00000000 00:00 
b7621000-b7700000 ---p 00000000 00:00 
b7796000-b7798000 rw-p 00000000 00:00 
b77aa000-b77ad000 rw-p 00000000 00:00 
bfd7f000-bfd94000 rw-p 00000000 00:00          [stack]
已放弃

我们可以在运行结果中看到报错:"double free or corruption (fasttop)",意思就是说两次释放了同一块内存导致出错,为什么会出现这样的错误呢?
在C++中,当类的成员变量中有指针变量时(通常该指针变量会通过 new 来指向一片内存空间),必须要自定义拷贝构造函数来实现深拷贝,否则会导致两次释放同一块内存,出现上述错误。
很明显在上述代码中,自定义的类 Point 中定义了指针类型的成员变量 int * p,但却没有自定义拷贝构造函数,所以会导致“ double free”错误。
那么问题来了,在将 Point 对象 push_back 到 vector 中时,Point 类的拷贝构造函数是如何被调用的呢?在C++中,调用拷贝构造函数一般有如下三种情形:
  • 用一个类对象A初始化另一个类对象B时;
  • 函数的参数是一个类对象时;
  • 函数的返回值是类对象时;
现在把代码稍作修改,来观察一下构造函数是如何被调用的:

class Point
{
public:
    Point();
    Point(const Point point);
    ~Point();
    void setPoint(int x, int y);
    void printPoint();
private:
    int xPos;
    int yPos;
    int p;
};
 
Point::Point()
{
    new int(10);
}
Point::Point(const Point point)
{
    cout << "calling copy constructor" << endl;
    xPos point.xPos;
    yPos point.yPos;
    new int();
    *p *(point.p);
}
Point::~Point()
{
    cout << "calling deconstructor" << endl;
    delete p;
}
 
void Point::setPoint(int x, int y)
{
    xPos x;
    yPos y;
}

void Point::printPoint()
{
    cout<< "x << xPos;
    cout<< << yPos << endl;
}

int main()  
  
    vector ivec;
    for (vector::size_type ix 0; ix 5; ++ix)
    {
        Point M;
        M.setPoint(ix 1, ix 1);
        // M.printPoint();
        ivec.push_back(M);
    }
    return 0;  

可以看到如下运行结果:

1
calling copy constructor
calling deconstructor
2
calling copy constructor
calling copy constructor
calling deconstructor
calling deconstructor
3
calling copy constructor
calling copy constructor
calling copy constructor
calling deconstructor
calling deconstructor
calling deconstructor
4
calling copy constructor
calling deconstructor
5
calling copy constructor
calling copy constructor
calling copy constructor
calling copy constructor
calling copy constructor
calling deconstructor
calling deconstructor
calling deconstructor
calling deconstructor
calling deconstructor
calling deconstructor
calling deconstructor
calling deconstructor
calling deconstructor
calling deconstructor

--------------------------------
Process exited with return value 0
Press any key to continue .

通过单步调试,可以发现在 push_back 时,拷贝构造函数在这些地方被调用了:
  • 当 ivec.push_back(M) 时,这里 M 是一个类对象,它作为参数会 调用一次拷贝构造函数
  • 我们知道,vector 可以动态扩展内存,这个动态扩展的过程是这样子的:当增加新元素时,如果超过当时的容量,则容量会扩充至原来的两倍。如果两倍容量仍不足,就扩张至足够大的容量。容量的扩张必须经历“重新配置,元素移动,释放原空间”这三个过程。其中在元素移动时,也就是将原来空间中的每一个类对象通过拷贝构造函数拷贝到新分配的内存空间时,也会 调用拷贝构造函数
其实,在考虑向 vector 中 push_back 类对象时,我们也可以考虑换一种方式,将指向类对象的指针 push_back 到 vector 中,这可以避免拷贝构造的过程, 当然不要忘记在不需要时 delete 分配的类对象的内存空间。代码可以稍作修改,来达到同样的效果:

vector ivec;      
for(vector::size_type ix 0; ix != 10; ++ix)

    Point *ptr new Point;
    ptr->setPoint(ix 1, ix 1);
    ptr->printPoint();
    ivec.push_back(ptr);    
}
.....
// 完成相关操作后,释放 new 的内存空间
for(vector::iterator itr = ivec.begin(); itr != ivec.end(); ++itr)
      delete itr;
ivec.clear();

参考资料:
1. STL源码剖析

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有