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

目录
  • 了解C++默默编写哪些函数
  • 不想使用编译器函数
  • 为多态基类声明virtual析构函数
  • 别让异常逃离析构函数
  • 绝不在构造和析构过程中调用virtual函数
  • 令operator=返回一个reference to *this
  • 在operator=中处理自我赋值
  • 复制对象时别忘了每个成分

了解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++构造析构赋值运算内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++赋值运算符

    C++当中允许类对象赋值,这是通过默认的重载赋值运算符实现的,它的原型如下: Class_name & Class_name::operator=(const Class_name &); 它接受并返回一个指向类对象的引用. 将已有的对象赋给另一个对象时,将会使用重载的赋值运算符: StringBad headline1("Celery"); StringBad knot; knot = headline1; // 调用赋值运算符 如果是对象初始化的过程,则不一定会使用

  • C++编程语言中赋值运算符重载函数(operator=)的使用

    目录 1 概述 1.1 Why 2 示例代码 2.1 示例代码1 2.2 示例代码2 3 总结 本文主要介绍 C++ 编程语言中赋值运算符重载函数(operator=)的相关知识,同时通过示例代码介绍赋值运算符重载函数的使用方法. 1 概述 1.1 Why 首先介绍为什么要对赋值运算符“=”进行重载.某些情况下,当我们编写一个类的时候,并不需要为该类重载“=”运算符,因为编译系统为每个类提供了默认的赋值运算符“=”,使用这个默认的赋值运算符操作类对象时,该运算符会把这个类的所有数据成员都进行一次

  • C++浅析构造函数的特性

    目录 构造函数的概念 构造函数的特性 只能有一个构造函数 构造函数的概念 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次. 构造函数的特性 (1)函数名与类名相同. (2)无返回值. (3)编译器自动调用对应的构造函数. (4)构造函数可以重载. 我们这里直接举一个例子 #include<iostream> using namespace std; class Data { public:

  • C++继承中的对象构造与析构和赋值重载详解

    目录 一.构造/析构顺序及继承性 二.拷贝构造的继承性 三.赋值重载不具有继承性 总结 一.构造/析构顺序及继承性 class A { private: int _a; public: A(int a = 0): _a(a) { cout << "A()" << this << endl; } ~A() { cout << "~A()"<< this <<endl; } }; class B :

  • C++踩坑实战之构造和析构函数

    目录 前言 构造函数 通过构造函数实现的类型转换 派生类的构造函数 析构函数 继承中的析构函数 应用 总结 前言 我是练习时长一年的 C++ 个人练习生,喜欢野指针.模板报错和未定义行为(undefined behavior).之前在写设计模式的『工厂模式』时,一脚踩到了构造.继承和 new 组合起来的坑,现在也有时间来整理一下了. 构造函数 众所周知:在创建对象时,防止有些成员没有被初始化导致不必要的错误,在创建对象的时候自动调用构造函数(无声明类型),完成成员的初始化.即: Class c

  • 一起来学习C++的构造和析构

    目录 1.构造函数 1.1构造函数长什么样子 1.2构造函数干嘛的 1.3思考 2.析构函数 2.1析构函数长什么样子? 2.2析构函数用来干嘛?(什么时候需要自己手动写析构函数) 3.拷贝构造函数 问题 4.深浅拷贝 (1)浅拷贝:默认的拷贝构造叫做浅拷贝 (2)深拷贝:拷贝构造函数中做了new内存操作,并且做拷贝赋值的操作 5.构造和析构顺序问题 6.C++结构体 答疑: 总结 1. 构造函数 1.1 构造函数长什么样子 (1) 函数名和类名相同 (2) 没有返回值 (3) 如果不写构造函数

  • 详解C++中赋值,关系,函数调用运算符重载的实现

    目录 赋值运算符重载 类结构 问题的出现 具体实现 关系运算符重载 类结构 具体实现 函数调用运算符重载 类结构 具体实现 总结 赋值运算符重载 在C++中基本数据类型例如整型,可以实现连续赋值:a=b=c:而我们的对象的成员属性虽然可以相等,但是如果牵扯到堆地址,就会有深浅拷贝的问题存在.所以我们自己重载赋值运算符,实现连等的方法. 类结构 class Info { int* m_a; public: Info() { m_a = NULL; } Info(int a) { m_a = new

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

    目录 了解C++默默编写哪些函数 不想使用编译器函数 为多态基类声明virtual析构函数 别让异常逃离析构函数 绝不在构造和析构过程中调用virtual函数 令operator=返回一个reference to *this 在operator=中处理自我赋值 复制对象时别忘了每个成分 了解C++默默编写哪些函数 当实现一个空类,c++会为你补上构造函数,拷贝构造函数,拷贝赋值运算符,析构函数 class Empty{}; //等于你写了 class{ public: Empty(){...};

  • JavaScript中eval()函数用法详解

    eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行. 如果参数是一个表达式,eval() 函数将执行表达式.如果参数是Javascript语句,eval()将执行 Javascript 语句. 语法 复制代码 代码如下: eval(string) 参数 描述 string 必需.要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句. eval()函数用法详解: 此函数可能使用的频率并不是太高,但是在某些情况下具有很大的作用,下面就介绍一下eva

  • PHP 的比较运算与逻辑运算详解

    1.以下值用 empty() 被判断为true: 未赋值变量.未声明变量.0."0"."".false.null.空数组 array() .对象的魔术方法 __get() 返回的值 在低于 PHP5.0 的版本中,没有任何属性的对象也被 empty 判断为 true 注意:empty() 只接受变量或变量的索引值或属性值,不能直接传入常量,也不能传入运算表达式,PHP 5.5 之后支持表达式 2.被 isset() 判断为 false 的值:未赋值变量.未声明变量.

  • Python的对象传递与Copy函数使用详解

    1.对象引用的传值或者传引用 Python中的对象赋值实际上是简单的对象引用.也就是说,当你创建一个对象,然后把它赋值给另一个变量的时候,Python并没有拷贝这个对象,而是拷贝了这个对象的引用.这种方式相当于值传递和引用传递的一种综合.如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过"引用传递"来赋值.如果函数收到的是一个不可变变量(比如数字.字符串或者元祖)的引用,就不能直接修改原始对象--相当于通过"值传递"来赋值.

  • JavaScript CollectGarbage函数案例详解

    首先看一个内存释放的实例: <SCRIPT LANGUAGE="JavaScript"> <!-- strTest = "1"; for ( var i = 0; i < 25; i ++ ) { strTest += strTest; } alert(strTest); delete strTest; CollectGarbage(); //--> </SCRIPT> CollectGarbage,是IE的一个特有属性,用

  • Python的函数使用详解

    目录 前言 1 跳出循环-break 2 python函数 2.1 内置函数 2.2 自定义函数 2.3 main函数 前言 在两种python循环语句的使用中,不仅仅是循环条件达到才能跳出循环体.所以,在对python函数进行阐述之前,先对跳出循环的简单语句块进行介绍. 1 跳出循环-break python提供了一种方便快捷的跳出循环的方法-break,示例如下,计算未知数字个数的总和: if __name__ == "__main__": sum = 0 while True:

  • Python数据分析之NumPy常用函数使用详解

    目录 文件读入 1.保存或创建新文件 2.读取csv文件的函数loadtxt 3.常见的函数 4.股票的收益率等 5.对数收益与波动率 6.日期分析 总结 本篇我们将以分析历史股价为例,介绍怎样从文件中载入数据,以及怎样使用NumPy的基本数学和统计分析函数.学习读写文件的方法,并尝试函数式编程和NumPy线性代数运算,来学习NumPy的常用函数. 文件读入 读写文件是数据分析的一项基本技能 CSV(Comma-Separated Value,逗号分隔值)格式是一种常见的文件格式.通常,数据库的

  • C++Vector容器常用函数接口详解

    目录 一.基础框架 二.迭代器实现 三.size capacity resize reserve 四.insert,erase 五.pop_back,push_back 六.operator[] 七.构造函数 析构函数 赋值重载 一.基础框架 template<class T> class vector { public: typedef T* iterator; typedef const T* const_iterator; private: iterator _start;//指向第一个

  • 基于js的变量提升和函数提升(详解)

    一.变量提升 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分. 上个简历的例子如: console.log(global); // undefined var global = 'global'; console.log(global); // global function fn () { console.log(a); // undefined var a = 'aaa';

  • C++中函数指针详解及代码分享

    函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,如同数组的名字就是数组的起始地址. 1.函数指针的定义方式:data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn); c语言函数指针的定义形式:返回类型 (*函数指针名称)(参数类型,参数类型,参数类型,-); c++函数指针的定义形式:返回类型 (类名

随机推荐