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

Apple Metal API对象析构问题:析构函数未执行原因排查

问题

运行Apple官方提供的metal-cpp示例代码时,能正常看到构造函数的打印输出,但始终看不到析构函数的打印内容。进一步测试发现,pSharedApp->run()之后的所有代码都没有执行——即使关闭应用窗口,程序退出码为0,断点和自定义打印语句都无响应。

简化后的代码如下:

#include <iostream>
#define NS_PRIVATE_IMPLEMENTATION
#define MTL_PRIVATE_IMPLEMENTATION
#define MTK_PRIVATE_IMPLEMENTATION
#define CA_PRIVATE_IMPLEMENTATION

#include <Metal/Metal.hpp>
#include <MetalKit/MetalKit.hpp>

class Renderer
{
    public:
        explicit Renderer( MTL::Device* pDevice );
        ~Renderer();

        void draw( MTK::View* pView );

    private:
        MTL::Device* _pDevice = nullptr;
        MTL::CommandQueue* _pCmdQueue = nullptr;
};

class AppView final : public MTK::ViewDelegate
{
    public:
        explicit AppView( MTL::Device* pDevice );
        ~AppView() override;
        
        void drawInMTKView( MTK::View* pView ) override;

    private:
        Renderer* _pRenderer = nullptr;
};

class AppDelegate final : public NS::ApplicationDelegate
{
    public:
        ~AppDelegate() override;

        static NS::Menu* create_menu_bar();

        void applicationWillFinishLaunching( NS::Notification* pNotification ) override;
        void applicationDidFinishLaunching( NS::Notification* pNotification ) override;
        bool applicationShouldTerminateAfterLastWindowClosed( NS::Application* pSender ) override;

    private:
        NS::Window* _pWindow = nullptr;
        MTK::View* _pView = nullptr;
        MTL::Device* _pDevice = nullptr;
        AppView* _pAppView = nullptr;
};

int main()
{
    auto* pool = NS::AutoreleasePool::alloc()->init();

    AppDelegate appDelegate;

    NS::Application* pSharedApp = NS::Application::sharedApplication();
    pSharedApp->setDelegate( &appDelegate );
    pSharedApp->run();

    pool->release();
    return 0;
}


// -----------------------------------------------  AppView  --------------------------------------------------------------------------------
AppView::AppView( MTL::Device* pDevice )
    : MTK::ViewDelegate()
    , _pRenderer( new Renderer( pDevice ) )
{ std::cout << "AppView Instantiated. \n"; }

AppView::~AppView()
{ delete _pRenderer; std::cout << "AppView Destroyed. \n";}

void AppView::drawInMTKView( MTK::View* pView )
{ _pRenderer->draw( pView ); }


// -----------------------------------------------  Renderer  --------------------------------------------------------------------------------
Renderer::Renderer( MTL::Device* pDevice )
    : _pDevice( pDevice->retain() )
{ _pCmdQueue = _pDevice->newCommandQueue(); std::cout << "Renderer Instantiated. \n";}

Renderer::~Renderer()
{
    std::cout << "Renderer Destroyed. \n";
    _pCmdQueue->release();
    _pDevice->release();
}


// -----------------------------------------------  AppDelegate  --------------------------------------------------------------------------------
AppDelegate::~AppDelegate()
{
    _pWindow->release();
    _pView->release();
    _pDevice->release();
    delete _pAppView;
}

void AppDelegate::applicationWillFinishLaunching( NS::Notification* pNotification )
{
    NS::Menu* pMenu = create_menu_bar();
    NS::Application* pApp = reinterpret_cast< NS::Application* >( pNotification->object() );
    pApp->setMainMenu( pMenu );
    pApp->setActivationPolicy( NS::ActivationPolicy::ActivationPolicyRegular );
}

void AppDelegate::applicationDidFinishLaunching( NS::Notification* pNotification )
{
    CGRect frame = (CGRect) {
        {260.f, 180.f},
        {1200.f, 750.f}
    };

    _pWindow = NS::Window::alloc()->init(
        frame,
        NS::WindowStyleMaskClosable | NS::WindowStyleMaskTitled,
        NS::BackingStoreBuffered,
        false );

    _pDevice = MTL::CreateSystemDefaultDevice();

    _pView = MTK::View::alloc()->init( frame, _pDevice );
    _pView->setColorPixelFormat( MTL::PixelFormat::PixelFormatBGRA8Unorm_sRGB );
    _pView->setClearColor( MTL::ClearColor::Make( 0.01, 0.01, 0.01, 1.0 ) );

    _pAppView = new AppView( _pDevice );
    _pView->setDelegate( _pAppView );

    _pWindow->setContentView( _pView );
    _pWindow->setTitle( NS::String::string( "00 - Window", NS::StringEncoding::UTF8StringEncoding ) );

    _pWindow->makeKeyAndOrderFront( nullptr );

    auto* pApp = reinterpret_cast< NS::Application* >( pNotification->object() );
    pApp->activateIgnoringOtherApps( true );
}

bool AppDelegate::applicationShouldTerminateAfterLastWindowClosed( NS::Application* pSender )
{
    return true;
}
原因与解决方法

核心原因

Cocoa应用的NSApplication::run()默认在应用终止时直接调用exit(),导致进程直接退出,不会执行run()之后的代码,栈上的局部变量(比如main里的AppDelegate appDelegate)无法触发析构函数,进而AppView和Renderer的析构逻辑也不会被执行。

解决步骤

1. 在AppDelegate中添加终止回调处理资源清理

重写applicationWillTerminate方法,手动释放动态分配的资源,确保析构函数被调用:

void AppDelegate::applicationWillTerminate(NS::Notification* pNotification)
{
    // 手动触发AppView和Renderer的析构
    delete _pAppView;
    _pAppView = nullptr;
    
    // 释放Cocoa对象
    _pWindow->release();
    _pWindow = nullptr;
    _pView->release();
    _pView = nullptr;
    _pDevice->release();
    _pDevice = nullptr;
}

同时修改AppDelegate的析构函数,避免重复释放已经清理过的资源:

AppDelegate::~AppDelegate()
{
    // 这里不再处理已经在applicationWillTerminate中释放的资源
}

2. 调整main函数中AppDelegate的存储方式

将AppDelegate改为堆分配,确保在run()之后可以手动触发其析构:

int main()
{
    auto* pool = NS::AutoreleasePool::alloc()->init();

    AppDelegate* pAppDelegate = new AppDelegate();

    NS::Application* pSharedApp = NS::Application::sharedApplication();
    pSharedApp->setDelegate( pAppDelegate );
    pSharedApp->run();

    // 手动释放AppDelegate,触发其析构逻辑
    delete pAppDelegate;
    pool->release();
    return 0;
}

额外提示

  • Cocoa应用的事件循环终止后直接退出进程是设计决定,不要依赖栈变量的析构处理关键资源清理。
  • 所有必须执行的清理逻辑,优先放在applicationWillTerminate等生命周期回调中,或使用智能指针管理C++对象的生命周期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 05:12:03