如何理解c++构造函数和析构函数

新手上路,请多包涵

如题,如何理解c++构造函数和析构函数

阅读 3.4k
5 个回答

构造函数

  • 构造对象时进行属性的初始化
  • 可以重载构造函数,从而有多种初始化方法,对属性进行初始化

析构函数

  • 对象要销毁之前调用,用于删除一些对象,回收内存空间(防止内存堵塞)
  • 在析构函数中可以释放一些指针指向的内存空间

我觉得,重要的有两点:

  1. 与编译器配合,减少人力编写初始化的重复代码
  2. 与编译器配合,减少人力编写异常处理时的重复代码

对于 #1,举例,一个对象具有其他对象作为成员,其初始化代码不必重复多份,甚至不必明写。

对于 #2,两方面,一是 C++ 的 throw 会退栈,也就是直到 catch 的地方前会依次析构这之前构造的栈变量。二是在每个 return 处不必像 C 一样复制一份栈变量清理代码。这些回退/析构代码的调用是编译器生成的。

{
    AutoLock al(someLock); // 构造函数写一个 m_lock.Lock()
    ...
} // 离开这里 al 就析构了,析构函数写一个 m_lock.Unlock()
{
    HANDLE rawHandle = OpenFile(..);
    if (!rawHandle) {
        return false;
    }

    AutoFileHandle handle(rawHandle); // 析构函数写一个 CloseHandle(m_handle)
    if (!ReadFile(handle, ...)) {
        return false; // 自动调析构
    }
    if (!ReadFile(handle, ...)) {
        throw ex; // 自动调析构
    }
}

类构造函数和析构函数

类的构造函数:定义这个类被new出一个实例的时候,上班前一定会做的准备工作流程
类的析构函数:定义这个类的实例结束自己的工作下班后,一定会做的整理准备下班的工作流程
实例:打工人个体
new xx():指派一个新的打工人专门负责一个具体事情、任务
类(的定义和实现代码):定义打工人每个个体的工作内容,如何处理情况

站着理解,坐着理解都行。。。
PS 学c++的应该至少见过RAII这个词

这个问题太宽泛了,粗略讨论一下。

构造函数和析构函数是类或结构体中的两类特殊函数。类和结构体,是一组属性的集合,一般用来描述某种特定类型的对象。对于某一对象而言,这些属性的取值一般是具有某种限制的,不满足这些限制的属性取值将导致该对象处于无效状态,并影响程序的执行。

简单来说,构造函数的目的是令对象在生成时各个属性的取值为有效状态,析构函数是为了回收对象持有的资源。如:

class Person {
    ...

private:
    unsigned _age;
    std::string _name;
};

Person 类有两个私有属性 _age_name。那么,对于一般情况,属性的取值限制大概有:

  • _age 属性的取值合理,如 0 <= _age <= 120
  • _name 有初始值,不能为空

当然,取值限制取决于程序需求,如果这个程序用 Person 表示神话人物,那么 _age 可以取到更大的值;如果某个人的名字可以待定,那取空值表示待定也没问题,这里讨论的取值情况是一种普遍情况。

_age 的取值有限制,并且必须取值,对于 unsigned 类型,其取值必大于0,所以在构造函数里应该检查该取值是否小于等于120,如果不符合条件,则视为错误情况,根据程序需求抛出异常或记录到日志中;

_name 必须有初始值,不允许无名称的对象存在。

构造函数的任务就是让程序在构造函数时满足上述条件。那么,

  • 默认构造函数 Person() 应该删除,需要的是有参数的构造函数,并且在构造时要检查 _age 是否是合理值;
  • 拷贝构造函数和赋值构造函数视程序要求,可以删除或保留,一般来说你不会希望一个人可以复制;
  • 移动拷贝构造函数和移动赋值构造函数可以保留,对象所有权的转移是可行的。

析构函数一般用于释放对象持有的资源,Person 对象通过 _name 间接持有堆对象,该资源会由 _name 释放,所以使用默认的析构函数即可。

实现如下:

#include <string>
#include <cassert>

class Person {
public:
    Person(unsigned age, std::string name)
        : _age(age)
        , _name(std::move(name)) {
            assert(_age <= 120);
        }

    Person(Person const &) = delete;
    Person & operator= (Person const &) = delete;

    Person(Person &&) = default;
    Person & operator= (Person &&) = default;

    ~Person() = default;

private:
    unsigned _age;
    std::string _name;
};

int main() {
    Person p1(13, "Zhang San");
    Person p2(p1);          // 错误,不允许拷贝构造
    Person p3(std::move(p1));
    Person p4 = std::move(p3);
}

以上代码中有一些语句是不必要的,比如两个 = delete。总结一下:

  • 默认构造函数:令对象可拥有有意义的空状态
  • 拷贝构造函数:对象可以拷贝,多个属性值相同的对象有意义
  • 赋值拷贝构造函数:同上
  • 移动构造函数:对象的所有权可以转移,并将失去所有权的对象设置为某种空状态
  • 赋值移动构造函数:同上
  • 析构函数:回收对象持有的资源,或做记录

从以上讨论可以看出,构造函数和析构函数如何编写是根据实际需求来设计的,不同的应用场景下同一对象可能有不同的要求,C++ 的构造函数和析构函数给了你充分表达的能力。与其它语言相比,C++ 给了代码编写者更多的自由,你几乎可以控制程序中的方方面面,代价是你需要仔细思考每一个细节。希望你能喜欢上 C++ 这门语言。

推荐问题