拷贝、赋值和析构函数(稍微进阶)

拷贝构造函数

基本形式

注意形参类型是类类型,不能声明为explicit,因为有些地方需要隐式转换

class Foo
{
public:
    Foo();
    Foo(const Foo&);
};

合成拷贝构造函数

你不声明构造函数,也会给你一个合成的。浅拷贝问题

调用时机

1.非引用类型形参

2.返回非引用类型

3.使用{}初始化一个数组

4.标准库容器的push或insert操作。emplace调用的是构造函数。

为啥形参是引用的

不是引用的话,会一直调用拷贝构造函数,会死循环下去。

拷贝初始化与explicit构造函数

vector构造函数为explicit类型,所以以下写法不正确。

vector<int> v1(10);  //直接初始化
veector<int> v2=10; //xxx,构造不被允许

解释:其实veector<int> v2=10; 包含两句意思,第一句是调用构造函数创一个临时对象,第二句是拷贝。

下列语句也不行:

void f(vector<int>);
f(10);  //xxx,也是不允许构造,即第一步都不行
f(vector<int>(10));  //正确

编译器优化: 

有时编译器会做出优化。把两步优化成一步。前提是拷贝构造是private,不是private直接在第二步拷贝时就报错了。

string null_book = "000000";  //包含两步,创建对象和拷贝构造

//优化成
string null_book("000000");

 赋值运算符

基本形式

class Foo
{
public:
    Foo& operator=(const Foo&);
};

当重载运算符函数是成员函数时,第一个参数就绑定到this指针。

Sales_data a1,a2;
a1 = a2; //调用=运算符

合成 赋值运算符

你不写也会送你一个合成的。浅拷贝问题


析构函数

基本形式

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

函数执行流程

构造函数先执行初始化列表,再执行函数体;析构函数先执行函数体,再销毁成员(逆序销毁)。

类的析构函数和指针类型成员

调用成员的析构函数,内置类型的成员没有析构函数。

注意不会自动帮内置指针成员调用delete p;

而智能指针是类类型,具有析构函数。

调用时机(句句箴言)

1.离开作用域被销毁时。

2.会调用对象的成员的析构函数。

3.容器(标准库容器或数组)被销毁时,调用元素的析构。

4. 对new这些动态创建的对象,你得调用delete,才会帮你调用析构。

5. 临时对象,它的完整表达式结束时。

注意局部指针或引用离开作用域时,不会自动帮你调用

合成析构函数

你不写,也会帮你合成一个,但功能受限。


三五法则

1.一般你定义了析构函数,说明里面有动态内存释放,所以你必须得定义析构函数。

class HasPtr
{
    public:
        HasPtr(const string&s):ps(new string(s)){}
        ~HasPtr(){
            delete ps;   //需要手动释放
        }
    private:
        string *ps;
};

如果你不自己定义拷贝函数的话,就会用合成的,会发生浅拷贝

HasPtr f(HasPtr p)  //这里一次拷贝
{
    HasPtr ret = p; //这里一次拷贝
    return ret;   //xxx,p和ret都被销毁了,delete两次
}

更有甚者,你一调用就销毁实参的成员了。

HasPtr p("some");
f(p);  //会释放p.ps
HasPtr q(p);  //xxx,都没了ps了

2. 定义了拷贝函数也必须定义赋值运算符,反之亦然,如果没有成员涉及动态内存分配,不需要再定义析构函数。

比如为对象分配一个新的序号

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章