ZT:对象生死劫 - 构造函数和析构函数的异常

本文探讨了构造函数和析构函数在对象生存周期中的角色,分析了C++和DELPHI语言中对象创建与销毁过程中的异常处理机制,并讨论了如何避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:http://blog.youkuaiyun.com/leadzen/archive/2007/09/13/1783116.aspx

原作者:李战

 

构造函数和析构函数分别管理对象的建立和释放,负责对象的诞生和死亡的过程。当一个对象诞生时,构造函数负责创建并初始化对象的内部环境,包括分配内存、创建内部对象和打开相关的外部资源,等等。而当对象死亡时,析构函数负责关闭资源、释放内部的对象和已分配的内存。

  在对象生死攸关的地方,如果程序代码出现问题,常常会发生内存泄漏,从而产生可能危害系统运行的孤魂野鬼。大量的事实表明,业务逻辑代码写得非常严谨的程序在运行中仍然发现存在内存泄露,大都是构造和析构部分的代码存在问题。

  而许多程序员都习惯于面向对象的编程,需要时就建立一个对象,不用时就将其释放。这样的习惯简化了我们的思路,正是面向对象编程思想带来的好处。也许有于太习惯,很多程序员都忽略了在对象生死的瞬间可能产生的问题,这却值得我们去认真反思。

  其实,对象生死间的异常问题是一个充满争议的问题。甚至不同的编程语言,在对象生死间的异常问题上也持不同的态度。

  C++语言说:一个对象在出生的过程中出现问题,那这个对象就是一个没有生命的怪胎。既然它不是一个完整的对象,就根本不存在析构或释放的说法。因此,C++在调用构造函数时产生异常,是不会调用对象的析构函数的,而仅仅释放对象的内存空间,之后就把异常抛给程序员处理。

  DELPHI (Object Pascal)语言认为:对象虽然在出生过程中出现异常,但它已经具有部分生命。既然是有生命的东西,都应该有死亡的权利。因此,DELPHI在调用构造函数时产生异常,一定会先析构函数,然后再抛出异常。

  那么,谁的观点对?谁的观点对?

  我想,这个问题争论上九九八十一天也未必有结果。因此,我们不必纠缠于观点的争论。只要我们知道了不同编程程语言有不同的处理方法就够了,当我们用哪种语言来编程时就尊重该种语言的观点,这才是务实的程序员应该做的!

  对于C++语言来说,由于构造函数产生异常时不会调用对应的析构函数,那么在构造函数里发生异常前的代码所创建的其他东西就不能被析构函数内的相关释放代码所释放。例如:

class TMyObject
{
  private:
    TOtherObject * OtherObject;
  public:
    TMyObject()
    {
      OtherObject = new TOtherObject();

      ...... //这后面的代码发生异常将导致OtherObject不会被释放!

    }
    ~TMyObject()
    {
      ......

      delete OtherObject;  //构造函数发生异常时析构函数根本不会被调用,此代码也不会被执行!
    }
}


   回想一下自己的程序中是否也存在类似的代码?如果是这样,那可就要注意了,这样的代码在C++中是不安全的!

  那么,应该怎样写才安全呢?

  事实上,如果在C++的构造函数里创建了其他东西,你就必须考虑构造函数发生异常的情况。在构造函数中发生异常时,已经创建的东西必须被释放掉,然后再重新抛出异常给上层调用代码处理,这才是C++构造函数中正确的异常处理方法。因此,前面的构造函数应该改写成下面的形式:

    TMyObject()
    {
      OtherObject = new TOtherObject();
      try
      {
      ...... //这里的代码发生异常。
      }
      catch(...)
      {
        delete OtherObject; //确保发生异常时,能释放掉已建立的东西。
        throw;  //再次抛出异常给上层调用代码处理。
      };
    }

  如果,一个构造函数要创建很多其他东西的话,就应该编写相应的try  try try ... catch catch catch形式的嵌套代码(或者相同逻辑的代码)来确保构造函数的正确性。

  不过,还有另外一种不用try...catch来确保构造异常时正确释放内存的方法,那就是C++标准类库中的那个著名的auto_ptr模板类。auto_ptr又常常被称为智能指针,它巧妙地利用C++退出作用域时会自动释放变量的机制,来清理其维护的对象。再看改写的代码:

#include <std.h>
 
class TMyObject
{
  private:
    std::auto_ptr<TOtherObject>  OtherObject;
  public:
    TMyObject()
    {
      OtherObject = new TOtherObject();

      ...... //这后面的代码发生异常,也可以确保OtherObject会被自动释放!

    }
    ~TMyObject()
    {
      //一旦将对象交给auto_ptr来维护,就永远不要自己释放该对象。因此,这里什么都不用写。
    }
}

  这样的代码那么简洁而且有效,也便于阅读和维护。我曾经也认为这里的OtherObject是类的成员,并不在构造函数的作用域内,因此异常跳出构造函数时并不会导致auto_ptr的析构函数被调用。但从创建对象的机制上来说,C++会先分配对象空间和创建成员对象,然后才调用构造函数的。一旦构造函数发生异常,C++一定会释放成员对象和空间,以丢弃这个不完整对象,然后才抛出异常。从这个层次上讲,auto_ptr成员的析构函数也会被正确调用。

  不过,atuo_ptr在使用过程中也有些副作用。比如,你把一个auto_ptr赋值给另一个auto_ptr,前一个auto_ptr就会变成null值,这不符合正常的赋值语义。这是由于auto_ptr重载了赋值操作符的缘故,不懂auto_ptr实现原理的人就常常犯null指针错误。使用auto_ptr还有一句名言:“别把一个对象赋给两个auo_ptr变量”,因为这会导致两次释放该对象。不管怎样,如果嫌try...catch麻烦,使用auto_ptr来保证不发生内存泄漏也是一个非常不错的选择。

  相比之下,DELPHI语言处理构造函数的异常就简单多了。因为DELPHI保证在构造函数发生异常后,会调用析构函数。不过并非所有的析构函数都满足这一条件,只有Destroy可以,而且它是一个虚函数。

TMyObject = class
  private
    OtherObject: TOtherObject;
  public
    constructor Create;
    destructor Destory; override;
end;

constructor TMyObject.Create;
begin
  OtherObject := TOtherObject.Create;

  ...... //这后面的代码发生异常可以保证析构函数Destory被调用,从而释放OtherObject!

end;

destructor TMyObject.Destory;
begin
  ......
  OtherObject.Free;  //这里OtherObject将被正确释放!
 
end;

  因为DELPHI的“构造异常时确保析构的机制”是非常基础的代码,它只能给根类TObject设计一个虚析构函数Destroy来作为析构函数调用。也就是说,如果你自己写的析构函数不是从Destory重载的,“构造异常时确保析构的机制”将失效!

  同时,DELPHI在根类TObject中提供了Free方法来方便对象释放。这个Free方法保证即使对象并为被创建(对象指针为nil),调用此方法也不会出错。这样,构造函数里面创建语句就可以很简洁地与析构函数的释放语句对应起来,方便我们看代码。看来,设计这个Free方法还是用心良苦啊!

  那么关于析构函数中的异常又会怎样呢?

   对象在死亡的过程中发生异常又引出一个有趣的问题,“想死死不了”或者“死了一半又不能死了”!那么,这个对象到底是死了还活着。这种既死又活的对象,就像量子理论中的那只“薛定谔的猫”一样有趣。的确存在,却难以琢磨!

  为此,C++根本不去纠缠这种复杂的问题,而是采用最简单的办法:如果析构函数抛出异常,将直接导致当前执行线程异常终止!如果是主线程中发生析构异常,程序立即退出!

  C++的这一做法是可以理解的,当代码已经走进无法想通的死胡同,对象只能疯掉,从而毁灭整个程序世界!看来并非芸芸众生才有无法逃脱的苦海,其实程序也有解不开的心结!

  所以,“永远不要在析构函数中抛出异常”成了编写C++代码的一条铁律!

  而在DELPHI中,析构函数发生异常和其他代码发生异常没有什么不同,发生异常之后的代码将不会被执行(finally部分的除外),异常将由能找到的上层异常处理代码来处理!如果异常最终没有被处理,才导致当前线程终止。

  我们来看下面的代码

destructor TMyObject.Destory;
begin
  C.Free;
  B.Free;  //释放B发生异常,将导致后面的代码不会被执行,A不会被释放!
  A.Free;
end;

  因此,严格地说,析构函数应该写成下面的形式:

destructor TMyObject.Destory;
begin
  try
    C.Free;
  finally
    try
      B.Free; //这里发生异常,也可以强行释放A
    finally
      A.Free;
    end;
  end;
end;

  但这样的代码感觉怪怪的。而且,虽然释放B时发生异常也可以确保释放后面的A,但B到底是释放了还是没释放?是不是也会有内存泄漏呢?这就要看B的析构函数是怎么写的,问题将一直追溯下去。照这样下去,那些try...fianlly仅仅能保证尽可能释放对象,而根本没法保证不发生内存泄漏。

  看来这个析构函数异常不是一时半会能想通的问题,DELPHI的方法也并不比C++高明。因此,“永远不要在析构函数中抛出异常”依然可以作为一个简单的定律!

  既然能保证编写的每一个析构函数都不发生异常,那么那些try...finally也就没有必要了。

  正如一首歌唱的那样:虽然这世界有太多不如意,但是我们的程序依久要继续...
 

<think>好的,我现在需要处理用户的问题。用户之前询问了关于`ubuntu-drivers devices`命令无效的情况,现在又提供了新的终端输出,看起来是在安装`ubuntu-drivers-common`后运行该命令的结果。用户可能遇到了安装过程中的问题,或者对输出有疑问。 首先,用户输入的命令是`sudo apt install ubuntu drivers-common`,但正确的包名应该是`ubuntu-drivers-common`,中间有连字符。用户可能输错了,导致安装失败或者部分安装。在用户提供的输出中,虽然有一些安装过程中的下载信息,但最后可能出现了错误。例如,用户可能看到依赖问题或包未找到的错误。 接下来,用户执行了`ubuntu-drivers devices`,但输出中显示了NVIDIA驱动列表,包括多个版本,其中推荐的是550版本。这说明在正确安装`ubuntu-drivers-common`后,命令可以运行,并检测到了NVIDIA显卡的可用驱动。然而,用户可能在安装过程中遇到了问题,或者在理解输出时有困惑。 需要检查用户提供的输出中是否有错误信息,比如404 Not Found,或者依赖无法满足的情况。例如,用户提供的URL中有`https://mirrors.tuna.tsinghua.edu.cn/ubuntu noble-updates/main amd64 ubuntu-drivers-common amd64 1:0.9.7.6ubl`,这里的版本号可能有误,可能是笔误,正确的应该是`0.9.7.6ubuntu3.2`。这可能导致安装失败,用户需要确认软件源是否正确配置。 另外,用户可能对输出中的驱动列表感到困惑,不知道应该选择哪个版本。需要解释推荐版本的意义,以及如何安装。例如,推荐使用550版本,用户可以通过`sudo apt install nvidia-driver-550`来安装。 还需要注意用户之前可能遇到的权限问题,或者路径问题,比如没有使用sudo,或者命令路径不正确。但根据当前输出,用户已经正确运行了命令,所以问题可能已经解决,但需要确认是否安装成功,或者是否仍有其他问题。 最后,用户可能在安装驱动后需要重启,或者处理内核模块冲突,比如屏蔽nouveau驱动。需要提醒用户这些步骤,以确保驱动正确加载。 总结来说,用户的问题可能涉及:1. 正确安装`ubuntu-drivers-common`包;2. 解决安装过程中的依赖或软件源问题;3. 选择合适的NVIDIA驱动版本并安装;4. 处理驱动冲突或加载问题。需要一步步引导用户检查这些方面,并提供解决方案。</think>根据你的终端输出,你已成功安装 `ubuntu-drivers-common` 并检测到 NVIDIA 显卡的多个可用驱动版本。以下是关键信息解析与操作建议: --- ### **1. 当前检测到的 NVIDIA 驱动选项** 命令 `ubuntu-drivers devices` 输出了以下可用驱动: ```plaintext driver : nvidia-driver-570-server - distro non-free driver : nvidia-driver-535-server-open - distro non-free driver : nvidia-driver-550-open - distro non-free driver : nvidia-driver-535-open - distro non-free driver : nvidia-driver-570-server-open - distro non-free driver : nvidia-driver-550 - distro non-free recommended <-- 推荐版本 driver : nvidia-driver-535 - distro non-free driver : nvidia-driver-535-server - distro non-free ``` - **推荐版本**:系统建议安装 `nvidia-driver-550`(标记为 `recommended`),这是经过测试与当前硬件系统兼容性最佳的版本。 --- ### **2. 安装推荐驱动** 直接安装推荐版本: ```bash sudo apt install nvidia-driver-550 ``` - 安装完成后**必须重启系统**以加载驱动: ```bash sudo reboot ``` --- ### **3. 验证驱动状态** 重启后检查 NVIDIA 驱动是否生效: ```bash nvidia-smi # 查看显卡状态 ``` - 若输出类似以下内容,说明驱动已成功加载: ```plaintext +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 | |-----------------------------------------+----------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+======================+======================| | 0 NVIDIA GeForce RTX 4060 ... Off | 00000000:01:00.0 Off | N/A | | N/A 45C P8 10W / 80W | 6MiB / 8192MiB | 0% Default | | | | N/A | +-----------------------------------------+----------------------+----------------------+ ``` --- ### **4. 驱动版本说明** | 驱动版本 | 类型 | 特点 | |------------------------|-----------|------------------------------------| | `nvidia-driver-550` | 标准版 | 推荐版本,稳定性性能均衡 | | `nvidia-driver-535` | 旧版 | 兼容旧硬件或特定应用 | | `*-server` 或 `*-open` | 服务器版/开源版 | 适用于服务器或需要开源协议的场景 | --- ### **5. 常见问题处理** #### **问题1:安装后黑屏/卡LOGO** - **原因**:可能与开源驱动 `nouveau` 冲突。 - **解决**: 1. 开机时进入 GRUB 菜单,选择 **Advanced options for Ubuntu** > **Recovery mode** > **root**。 2. 屏蔽 `nouveau` 驱动: ```bash echo "blacklist nouveau" | sudo tee /etc/modprobe.d/blacklist-nouveau.conf sudo update-initramfs -u sudo reboot ``` #### **问题2:驱动无法加载** - 检查内核模块: ```bash lsmod | grep nvidia # 若无输出,说明驱动未加载 ``` - 重新生成内核配置: ```bash sudo apt install --reinstall nvidia-dkms-550 sudo reboot ``` --- ### **6. 软件源优化(可选)** 若下载速度慢,可切换国内镜像源(如清华源): ```bash sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list sudo apt update ``` --- ### **总结步骤** 1. 安装推荐驱动:`sudo apt install nvidia-driver-550` 2. 重启系统:`sudo reboot` 3. 验证驱动:`nvidia-smi` 若仍有问题,请提供完整报错信息或截图!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值