You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++单例对象被销毁引发访问违规,如何维持派生类对象存活?

派生类单例对象存活期问题:地址访问违规原因与解决

问题描述

编写包含可重写方法的基类File,其内部维护一个静态单例指针。构造派生类File_Handler时,单例指针会指向该派生类对象,但实际使用中,单例仅在派生类对象存活期间有效,后续访问单例会触发地址访问违规错误。需要解决派生类对象的存活期问题,确保单例指针始终指向有效对象。

相关代码

基类头文件(File.h)

#pragma once

#include <string>

class File
{
    static File *singleton;

public:
    File();
    ~File();

    virtual char *read(std::string path);
    virtual void write(std::string path, const char *buffer);

    static File *get_singleton();
};

基类实现(File.cpp)

#include "File.h"
#include "../OS.h"

File *File::singleton = nullptr;

File::File()
{
    File::singleton = this;
}

File::~File()
{
}

char *File::read(std::string path)
{
    return nullptr;
}

void File::write(std::string path, const char *buffer)
{
}

File* File::get_singleton()
{
    return singleton;
}

派生类头文件(File_Handler.h)

#pragma once

#include <core/os/File.h>

class File_SDL : public File
{
public:
    File_Handler();

    char *read(std::string path) override;
    void write(std::string path, const char *buffer) override;
};

派生类实现(File_Handler.cpp)

#include "File_Handler.h"

#include <core/OS.h>

File_Handler::File_Handler() : File()
{
}

char *File_Handler::read(std::string path)
{
}

void File_Handler::write(std::string path, const char *buffer)
{
}

崩溃复现代码

void other_function()
{
    // 触发地址访问违规
    File::get_singleton()->write("file.txt", "Hello, World!");
}

void main()
{
   File_Handler handler = File_Handler();
   other_function();
}

问题原因分析

  1. 临时对象销毁导致悬空指针:
    File_Handler handler = File_Handler(); 执行时会先创建一个临时File_Handler对象,此时基类构造函数将singleton指向该临时对象。随后通过拷贝构造创建handler,这个过程不会调用基类的默认构造,因此singleton仍指向临时对象的地址。临时对象在拷贝完成后立即销毁,导致singleton变成悬空指针,后续访问时触发内存错误。
  2. 基类析构函数非虚:
    基类File的析构函数未声明为虚函数,后续若通过基类指针销毁派生类对象,会导致派生类部分资源无法正确释放,引发内存泄漏(虽不是当前崩溃的直接原因,但属于潜在问题)。

解决方案

方案1:避免临时对象拷贝,直接初始化栈对象

将main函数中的对象初始化方式改为直接构造,避免临时对象的创建与销毁:

void main()
{
   File_Handler handler; // 直接构造,无临时对象
   other_function();
}

此时singleton指向栈上的handler对象,该对象在main函数结束前始终有效,other_function()调用时不会触发悬空指针错误。

方案2:使用堆分配或静态对象延长生命周期

堆分配对象(手动管理生命周期)

void main()
{
   File_Handler* handler = new File_Handler(); // 堆上创建对象
   other_function();
   // 程序结束前手动销毁对象
   delete handler;
}

也可使用智能指针自动管理内存:

#include <memory>

void main()
{
   std::unique_ptr<File_Handler> handler = std::make_unique<File_Handler>();
   other_function();
   // 智能指针会在作用域结束时自动销毁对象
}

静态局部对象(全局存活)

在全局函数中定义静态局部对象,其生命周期覆盖整个程序运行期:

File* init_file_handler() {
    static File_Handler handler;
    return &handler;
}

void main()
{
   init_file_handler(); // 初始化静态对象,singleton指向它
   other_function();
}

方案3:禁用拷贝构造与赋值操作,从源头避免问题

在基类File中显式禁用拷贝构造和赋值运算符,防止因拷贝导致的临时对象问题:

class File
{
    static File *singleton;

public:
    File();
    ~File();

    // 禁用拷贝构造与赋值
    File(const File&) = delete;
    File& operator=(const File&) = delete;

    virtual char *read(std::string path);
    virtual void write(std::string path, const char *buffer);

    static File *get_singleton();
};

此时File_Handler handler = File_Handler();会触发编译错误,强制开发者使用安全的初始化方式。

方案4:修正基类析构函数为虚函数

为避免后续潜在的内存泄漏问题,将基类的析构函数声明为虚函数:

virtual ~File();

这样当通过基类指针销毁派生类对象时,会正确调用派生类的析构函数。


内容的提问来源于stack exchange,提问作者CosmoXD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 22:41:11