C++构造析构赋值运算函数应用详解

 更新时间:2022年09月24日 14:25:29   作者:RolleX  
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用;析构函数主要作用在于对象销毁前系统自动调用,执行一 些清理工作

了解C++默默编写哪些函数

当实现一个空类,c++会为你补上构造函数,拷贝构造函数,拷贝赋值运算符,析构函数

class Empty{};
//等于你写了
class{
public:
    Empty(){...};
    Empty(const Empty& rhs){...};
    ~Empty(){...};
    Empty& operator=(const Empty& rhs){...}
};

当这些函数被调用是,才会被编译器创建出来。如果你自己声明了一个构造函数,编译器将不会创建默认构造函数。

当然,编译器有时会拒绝生成operator=

class A{
public:
    NameObject{string& name};
private:
    string& nameValue;//为一个字符串引用
}
string newDog("peter");
string oldDog("fx")
A a(newDog);
A b(olddog);
a=b;//此处赋值就会导致a的nameValue绑定到不同对对象上面 而引用一旦绑定无法更改 是错误的

所以如果打算在一个含有引用成员的class中支持赋值操作,必须要自己定义operator=()

不想使用编译器函数

若不想使用编译器自动生成的函数,就该明确拒绝

如果一个类你想让它是独一无二,无法被拷贝的,则需要拒绝编译器生成的copy构造函数和copy赋值运算符。

方法一:将copy构造函数和copy赋值运算符函数声明为private并且故意不实现

class A{
public:
    ...
private:
    A(const A&);
    A& operator=(const A&);//只声明不实现
}

因为所有编译器生成的版本都是public。声明一个成员函数,阻止了编译器自己创建它,而声明为private,又可以阻止别人调用它。

客户试图拷贝A对象时,编译器会阻止,而当在member或friend函数之内调用时,连接器阻止.

方法二:写一个父类,将copy构造函数和copy赋值运算符函数声明为private并且故意不实现,再继承这个父类。

class A{
protected:
    A(){}
    ~A(){}
private:
    A(const A&);
    A& operator=(const A&)
}

为多态基类声明virtual析构函数

我的理解是:如果不这样做,在delete父类的指针的时候无法使用多态性质,只会删除掉父类的部分,而子类的部分会无法删除,造成局部销毁。

class A{
public:
    A();
    ~A();
};
class B:public A{};
//如果设计一个函数 返回一个基类的指针 可以使用多态 来自动判断是调用A还是B的析构来删除对象
A* base_pointer=getPointer();
delete base_pointer;
//当子类用一个指向父类的指针来执行删除,若父类的析构函数不是virtual,则无法调用子类的析构函数
//导致只删除 子类中父类的部分 剩下子类独有的部分

所以:无虚不基

base class的析构函数一定得是vritual,且可以推广到其他函数,若一个class里面没有一个virtual函数,那它不适合当一个base class。

class A{
public:
    A();
    virtual ~A();//应该这么搞
};
class B:public A{};
//如果设计一个函数 返回一个基类的指针 可以使用多态 来自动判断是调用A还是B的析构来删除对象
A* base_pointer=getPointer();
delete base_pointer;

但是,析构函数不能无端声明为virtual,因为声明为virtual需要虚表指针(vptr),vptr也是要占内存的,会增加对象的体积,减缓运行速度。

所以:当class至少含有一个virtual函数,才为它声明vritual虚构函数

当然,也可以将虚构函数声明为纯虚函数,使该class成为一个抽象基类,注意:必须为这个纯虚函数提供一份定义。因为析构函数是从派生类开始往基类调用,所以编译器会在A的派生类的析构函数中调用~A()。

class A{
public:
    virtual~ A()=0;
}
A::~A(){}//必须为这个纯虚函数提供一份定义

别让异常逃离析构函数

C++不喜欢析构函数出现异常。

可以在发生异常时终止程序,也可以吞下发生的异常

A::~A()
{
    try{ a.close();}
    catch(...){
        ...
        //制作运转记录,记录下close的失败
        std::abort();//终止程序
    }
}
A::~A()
{
    try{ a.close();}
    catch(...){
        ...
       //记录下close的调用失败
    }
}

如果某个操作可能在失败时抛出异常,而又必须要处理这个异常,这个异常必须来自析构以外的函数。(这里其实不太理解,文中给的例子是用一个新的成员函数来直行关闭)

绝不在构造和析构过程中调用virtual函数

我的理解是:在构造和析构的过程中调用的virtual成员函数并没有多态性质(注意该虚函数不是指析构函数和构造函数是虚函数,而是除此之外的一个成员函数)

class A{
public:
    A();
    vritual void xxx() const =0;
};
A::A(){
    ...
    xxx();
}
class B:public A{
public:
    virtual xxx() const;
};
//当执行
B b;

派生类的base class成分会在派生类自身成分构造之前先构造,而A的构造函数调用了虚函数xxx,这时xxx是A的xxx,而不会多态调用B的xxx,即使目前是在创建B对象。

根本原因是:在派生类的基类构造期间,对象的类型是基类而不是派生类,只有当派生类自己的部分开始执行时,该对象才变成一个派生类。

该道理同样用于析构函数

改法为:将A类的xxx改为non-vritual,在派生类的构造函数传递必要信息给基类的构造函数

class A{
public:
    A();
    void xxx() const =0;
};
A::A(){
    ...
    xxx();
};
class B:public A{
public:
    B(parameters):A(createXXX(parameters))
    {...}
private:
    static string createXXX(parameters);
};

在构造期间,令派生类将必要的构造信息向上传递给基类的构造函数。

令operator=返回一个reference to *this

为了实现连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧,这是为class实现赋值操作符时必须遵守的协议。

//连锁赋值
int x,y,z;
x=y=z=1;
class A{
public:
    A& operator=(const A& rhs)
    {
        return *this;
    }
}

在operator=中处理自我赋值

自我赋值发生在对象赋值给自己本身,例如*px=*py;

px和py都指向一个对象,则是一个自我赋值。

常发生在用引用赋值,指针赋值,多态等

可能会引发delete时将赋值和被赋值对象都删除了

避免方法:

1、证同测试

A &A operator=(const A& rhs){
    if(this==&rhs) return *this;//一样则什么也不做 直接返回
    ...
}

2、调整语序

class B{...};
class A{
private:
    B* pb;
}
A& A::operator=(const A& rhs){
    B* p=pb;//先记住原先的pb
    pb=new B(*rhs.pb);//令pb指向rhs.pb指向的一个副本,完成赋值
    delete p;//删除原来的pb
    return *this;
}

3、使用copy and swap,不大推荐

class A{
    ...
    void swap(A &rhs);
    ...
}
A& A::operator=(const A& rhs){
    A temp(rhs);//拷贝rhs的副本
    swap(temp);//交换
    return *this;
}

复制对象时别忘了每个成分

如果自己定义了拷贝构造函数,编译器将不会提醒你是否拷贝完所有成分,如果为class添加一个成员变量,则必须修改所有的copy函数和非标准形势的operator=

给派生类写copy函数的时候,也要复制它的基类的成分,那些成分往往是private,所以要让派生类调用相应的base class函数

B::B(const B &rhs):A(rhs),//调用基类的copy构造
...//对派生类部分初始化
{}
B::operator=(const B& rhs){
    A::operator=(rhs);//对基类部分赋值
    ...//对派生类部分赋值
    return *this;
}

编写copy函数时:

1、复制所有local成员变量

2、调用所有base classes内的适当copying函数

且不要尝试以某个copy函数实现另一个copy函数

到此这篇关于C++构造析构赋值运算函数应用详解的文章就介绍到这了,更多相关C++构造析构赋值运算内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言并查集的非递归实现详解

    C语言并查集的非递归实现详解

    以下是对C语言并查集的递归实现与非递归实现代码进行了详细的介绍,需要的朋友可以过来参考下,希望能够给你带来帮助
    2021-09-09
  • 一文详解C++中隐含的this指针

    一文详解C++中隐含的this指针

    这篇文章主要带大家详细了解一下C++中隐含的this指针,文中通过代码示例和图文介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-01-01
  • C++新特性详细分析基于范围的for循环

    C++新特性详细分析基于范围的for循环

    C++11这次的更新带来了令很多C++程序员期待已久的for range循环,每次看到javascript, lua里的for range,心想要是C++能有多好,心里别提多酸了。这次C++11不负众望,再也不用羡慕别家人的for range了。下面看下C++11的for循环的新用法
    2022-04-04
  • c语言中static的用法详细示例分析

    c语言中static的用法详细示例分析

    以下是对c语言中static函数的用法进行了详细的分析介绍,需要的朋友可以过来参考下
    2013-08-08
  • C语言实现顺序表的基本操作的示例详解

    C语言实现顺序表的基本操作的示例详解

    顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。本文将通过示例为大家讲解一下顺序表的基本操作,需要的可以参考一下
    2022-11-11
  • C语言实现投票系统

    C语言实现投票系统

    这篇文章主要为大家详细介绍了C语言实现投票系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • typedef_struct与struct之间的区别

    typedef_struct与struct之间的区别

    本篇文章主要是对typedef struct与struct之间的区别进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-12-12
  • 关于C++出现Bus error问题的排查与解决

    关于C++出现Bus error问题的排查与解决

    项目代码中经常出现莫名其妙的Bus error问题,并且代码中增加很多try catch 后依然不能将错误捕获,一旦Bus erro出现,进程直接崩溃掉,所以本文给大家介绍了关于C++出现Bus error问题的排查与解决,需要的朋友可以参考下
    2024-01-01
  • C++ 中assert()函数用法总结

    C++ 中assert()函数用法总结

    这篇文章主要介绍了C++ 中assert()函数用法总结的相关资料,需要的朋友可以参考下
    2017-07-07
  • 一文详解C++模板和泛型编程

    一文详解C++模板和泛型编程

    这篇文章主要为为大家为大家详细的介绍了C++模板和泛型编程使用,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05

最新评论