程序员开发实例大全宝库

网站首页 > 编程文章 正文

禁止拷贝构造,禁止bug(禁止粘贴复制怎么处理)

zazugpt 2024-09-01 07:55:49 编程文章 25 ℃ 0 评论

禁止拷贝构造,禁止bug



一、前言

首先,我先讲讲为什么会写这篇文章;这个也是翻阅自己之前博客,当时看开源代码的时候,总是很奇怪,为什么有的代码中会会出现类似于Epoll( const Epoll& ) = delete;这样的代码产生,当时大概查阅了一下资料,只是说这个代码的意思是将默认的拷贝构造函数禁止了,但是并没有了解到为什么这样做,直到前几天思考了这个问题,觉得有必要写下来,并且分享一下,也为自己做一个笔记,在今后的开发过程中,多留一个心眼,少踩一个坑~


二、拷贝构造函数

我们先来认识下什么是拷贝构造函数;

拷贝构造函数是一个特殊的构造函数,一般只有一个参数,这个参数一般是用const修饰的,对自己类的一个引用。

在编写程序的时候,如果我们没有编写拷贝构造函数,那么编译器会为我们自动生成一个拷贝构造函数。

下面看一下最简单的构造函数使用吧(使用默认构造函数)

#include <iostream>

using namespace std;

class CTest
{
public:
    CTest(){}
    ~CTest(){}
};

int main()
{
    CTest A;
    CTest B = A;
    return 0;
}

我们自己编写一个简单的拷贝构造函数:

#include <iostream>

using namespace std;

class CTest
{
public:
    CTest(){}
    CTest(const CTest& C)
    {
        cout<<"call me CTest"<<endl;
    }
    ~CTest(){}
};

int main()
{
    CTest A;
    CTest B = A;
    return 0;
}

结果:


什么情况下会调用拷贝构造函数?

主要有以下几方面:

  • 对象以值作为函数参数传递 代码演示: #include <iostream>
    using namespace std;
    class CTest
    {
    public:
    CTest(
    int num)
    {
    nNum = num;
    }
    CTest(
    const CTest& C)
    {
    nNum = C.nNum;
    cout<<"call me CTest"<<endl;
    }
    int getNum()
    {
    return this->nNum;
    }
    ~CTest(){}
    private:
    int nNum;
    };
    void g_Fun(CTest C)
    {
    cout<<"C:nNum"<<C.getNum()<<endl;
    }
    int main()
    {
    CTest
    A(10);
    g_Fun(A);
    return 0;
    }
    结果: 具体对象构造过程: g_Fun 函数会产生临时变量C(void g_Fun(CTest C))调用拷贝构造函数赋值Cg_Fun函数执行完毕后,会析构掉临时变量
  • 一个对象初始化另外一个对象 例子:(开始的案例) int main()
    {
    CTest A;
    CTest B = A;
    return 0;
    }

还有一种说法是当对象作为值的形式作为函数的额返回值

比如:

#include <iostream>

using namespace std;

class CTest
{
public:
    CTest(int num)
    {
        nNum = num;
    }
    CTest(const CTest& C)
    {
        nNum = C.nNum;
        cout<<"call me CTest"<<endl;
    }
    int getNum()
    {
        return this->nNum;
    }
    ~CTest(){}
private:
    int nNum;
};


CTest g_fun()
{
    CTest tmp(0);
    return tmp;
}

int main()
{
    g_fun();
    return 0;
}

这种形式的调用拷贝构造函数,根据编译器不同就会被优化,经过测试windows 环境下vs调式是会调用拷贝构造函数的,但是在linux下g++编译后,就进行了优化,直接通过堆栈返回对象,少调用一次拷贝构造函数。


三、禁用拷贝构造函数

上面我们回顾了拷贝构造函数的概念以及会调用拷贝构造函数的场景,感觉并没有异常发生,一切都函数那么友好,那么顺利,无论使用我们编写的构造函数还是使用默认拷贝构造函数,那么我们看一个例子,也许大家会有一些思考:

看下这个案例:

#include <iostream>

using namespace std;

class CTest
{
public:
    CTest(string arg):name(arg),pStr(new char[10])
    {
    }
    ~CTest()
    {
        delete pStr;
    }

private:
    string name;
    char *pStr;
};

int main()
{
    CTest C("test");
    CTest A = C;
    return 0;
}

先不要看答案,大家可以思考下~

我们可以编译运行下,会出现什么现象:

root@iZuf67on1pthsuih96udyfZ:~/GDB/20201014# g++ -std=c++11 CopyConstruct.cpp 
root@iZuf67on1pthsuih96udyfZ:~/GDB/20201014# ./a.out 
*** Error in `./a.out': double free or corruption (fasttop): 0x000000000136ac20 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fb1f13b97e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7fb1f13c237a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fb1f13c653c]
./a.out[0x400caa]
./a.out[0x400b7a]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fb1f1362830]
./a.out[0x400a19]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:01 131851                             /root/GDB/20201014/a.out
00601000-00602000 r--p 00001000 fd:01 131851                             /root/GDB/20201014/a.out
00602000-00603000 rw-p 00002000 fd:01 131851                             /root/GDB/20201014/a.out
01359000-0138b000 rw-p 00000000 00:00 0                                  [heap]
7fb1ec000000-7fb1ec021000 rw-p 00000000 00:00 0 
7fb1ec021000-7fb1f0000000 ---p 00000000 00:00 0 
7fb1f1039000-7fb1f1141000 r-xp 00000000 fd:01 925468                     /lib/x86_64-linux-gnu/libm-2.23.so
7fb1f1141000-7fb1f1340000 ---p 00108000 fd:01 925468                     /lib/x86_64-linux-gnu/libm-2.23.so
7fb1f1340000-7fb1f1341000 r--p 00107000 fd:01 925468                     /lib/x86_64-linux-gnu/libm-2.23.so
7fb1f1341000-7fb1f1342000 rw-p 00108000 fd:01 925468                     /lib/x86_64-linux-gnu/libm-2.23.so
7fb1f1342000-7fb1f1502000 r-xp 00000000 fd:01 925465                     /lib/x86_64-linux-gnu/libc-2.23.so
7fb1f1502000-7fb1f1702000 ---p 001c0000 fd:01 925465                     /lib/x86_64-linux-gnu/libc-2.23.so
7fb1f1702000-7fb1f1706000 r--p 001c0000 fd:01 925465                     /lib/x86_64-linux-gnu/libc-2.23.so
7fb1f1706000-7fb1f1708000 rw-p 001c4000 fd:01 925465                     /lib/x86_64-linux-gnu/libc-2.23.so
7fb1f1708000-7fb1f170c000 rw-p 00000000 00:00 0 
7fb1f170c000-7fb1f1722000 r-xp 00000000 fd:01 918031                     /lib/x86_64-linux-gnu/libgcc_s.so.1
7fb1f1722000-7fb1f1921000 ---p 00016000 fd:01 918031                     /lib/x86_64-linux-gnu/libgcc_s.so.1
7fb1f1921000-7fb1f1922000 rw-p 00015000 fd:01 918031                     /lib/x86_64-linux-gnu/libgcc_s.so.1
7fb1f1922000-7fb1f1a94000 r-xp 00000000 fd:01 265161                     /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fb1f1a94000-7fb1f1c94000 ---p 00172000 fd:01 265161                     /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fb1f1c94000-7fb1f1c9e000 r--p 00172000 fd:01 265161                     /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fb1f1c9e000-7fb1f1ca0000 rw-p 0017c000 fd:01 265161                     /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fb1f1ca0000-7fb1f1ca4000 rw-p 00000000 00:00 0 
7fb1f1ca4000-7fb1f1cca000 r-xp 00000000 fd:01 925451                     /lib/x86_64-linux-gnu/ld-2.23.so
7fb1f1eb3000-7fb1f1eb9000 rw-p 00000000 00:00 0 
7fb1f1ec8000-7fb1f1ec9000 rw-p 00000000 00:00 0 
7fb1f1ec9000-7fb1f1eca000 r--p 00025000 fd:01 925451                     /lib/x86_64-linux-gnu/ld-2.23.so
7fb1f1eca000-7fb1f1ecb000 rw-p 00026000 fd:01 925451                     /lib/x86_64-linux-gnu/ld-2.23.so
7fb1f1ecb000-7fb1f1ecc000 rw-p 00000000 00:00 0 
7ffdd4d0a000-7ffdd4d2b000 rw-p 00000000 00:00 0                          [stack]
7ffdd4def000-7ffdd4df2000 r--p 00000000 00:00 0                          [vvar]
7ffdd4df2000-7ffdd4df4000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted
root@iZuf67on1pthsuih96udyfZ:~/GDB/20201014# 

发现运行的直接崩溃了~

就我们获取下core文件,然后看下堆栈信息:

root@iZuf67on1pthsuih96udyfZ:~/GDB/20201014# gdb a.out core 
(gdb) bt
#0  0x00007ffff74aa428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff74ac02a in __GI_abort () at abort.c:89
#2  0x00007ffff74ec7ea in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7ffff7605ed8 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007ffff74f537a in malloc_printerr (ar_ptr=<optimized out>, ptr=<optimized out>, str=0x7ffff7605fa0 "double free or corruption (fasttop)", action=3) at malloc.c:5006
#4  _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3867
#5  0x00007ffff74f953c in __GI___libc_free (mem=<optimized out>) at malloc.c:2968
#6  0x0000000000400caa in CTest::~CTest (this=0x7fffffffe310, __in_chrg=<optimized out>) at CopyConstruct.cpp:13
#7  0x0000000000400b7a in main () at CopyConstruct.cpp:23

从堆栈信息大致可以得出,我们程序出现异常的是在23行,并且在析构的时候发生了异常;

所以得出结论:

不是所有的默认拷贝构造函数都是安全的,在我们不需要拷贝构造函数的时候,我们可以把默认拷贝构造函数禁止使用,这样就不会出现因为默认拷贝构造函数导致难以寻找的BUG!!!


四、如何禁止拷贝构造函数

禁止拷贝构造构造函数有两种方式,一般我们使用第一种,是最简单的,也是C++11的新特性~

  • 使用delete关键字 我们可以直接使用delete关键字进行禁止默认拷贝构造函数 public:
    Epoll(
    const Epoll& ) = delete;
  • 使用NonCopyable 基类 class NonCopyable
    {
    protected:
    NonCopyable(
    const NonCopyable&) = delete;
    NonCopyable&
    operator=(const NonCopyable&) = delete;
    NonCopyable() =
    default;
    ~NonCopyable() =
    default;
    };
    我们可以使用: class A : public NonCopyable {};

使用以上两种方式,我们可以禁止默认拷贝函数,如果需要拷贝构造函数,我们可以自己进行编写程序;

禁止默认拷贝构造,禁止BUG!


往期精彩汇总

GDB 多线程之旅

肝!动态规划

C++使用锁注意事项

呕心沥血的递归

muduo源码剖析学习总结

windows程序崩溃调试终极武器


本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表