单例模式的使用问题-声明周期过长

问题描述

这两天在学习使用Qt写一个登录界面,该界面的需求是:程序通过QTcpSocket连接服务器,根据连接的状态不同显示界面的图标。为了在其他界面中也可以使用QTcpSocket这个对象和服务器交互,故使用了单例模式。但是在手动点击关闭按钮时,程序报了错!!!!
在这里插入图片描述

在tinydiskclient.cpp中的代码如下:

TinyDiskClient &TinyDiskClient::getInstance()
{
    static TinyDiskClient instance;
    return instance;
}

QTcpSocket *TinyDiskClient::getTcpSocket()
{
    return m_tcpSocket;
}
void TinyDiskClient::initMember()
{
    m_tcpSocket = new QTcpSocket();
    connect(m_tcpSocket,SIGNAL(connected()), this, SLOT(slot_Connected()));
    connect(m_tcpSocket,SIGNAL(disconnected()),this,SLOT(slot_DisConnected()));
    connect(m_tcpSocket,SIGNAL(readyRead()),this,SLOT(slot_RecvMsg()));

    reConnectTimer = new QTimer();
    connect(reConnectTimer,&QTimer::timeout, this, &TinyDiskClient::slot_ReConnect);
}
// 连接上服务器后,设置窗口图标
void TinyDiskClient::slot_Connected()
{
    this->setWindowIcon(QIcon(QPixmap(":/img/tinyDisk.png")));
    this->setWindowTitle("登录");
    reConnectTimer->stop();
}
// 断开服务器后,重新设置图标
void TinyDiskClient::slot_DisConnected()
{
	// 出现问题的代码
    this->setWindowIcon(QIcon(QPixmap(":/img/tinyDiskDisConnected.png")));
    reConnectTimer->start(3000);
}

// 断开连接后重连服务器
void TinyDiskClient::slot_ReConnect()
{
    if(m_tcpSocket->state() == QAbstractSocket::UnconnectedState){
        MYLOG<<"连接服务器中...";
        m_tcpSocket->connectToHost("127.0.0.1", 8888);
        // m_tcpSocket->connectToHost(m_strIP, m_usPort);
    }
}

在main.cpp的代码如下:

#include "tinydiskclient.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // TinyDiskClient w;
    // w.show();
    TinyDiskClient::getInstance().show();
    QObject::connect(&a, &QApplication::aboutToQuit, []() {
        qDebug() << "Application is about to quit!";
    });
    return a.exec();
}

问题排查

添加了如下代码:程序显示结果
在这里插入图片描述可以看出:在点击界面关闭按钮后,首先调用closeEvent(),接着QApplication发出退出信号,最后在析构函数中。
当程序运行到析构函数中 时,QTcpSocket对象还处于连接状态,所以在断开连接的时候,会调用TinyDiskClient::slot_DisConnected()修改程序图标。可是程序界面已经没有了,随后报错QPixmap: Must construct a QGuiApplication before a QPixmap。

// tinydiskclient.cpp
TinyDiskClient::~TinyDiskClient()
{
    MYLOG<<"析构函数中:"<<" QTcpSocket状态:"<<m_tcpSocket->state()<<":"<<QApplication::activeWindow();
    if (m_tcpSocket->state() == QAbstractSocket::ConnectedState) {
        m_tcpSocket->disconnectFromHost();
    }
    delete reConnectTimer;
    delete m_tcpSocket;
    delete ui;
}

void TinyDiskClient::closeEvent(QCloseEvent *event)
{
    // m_tcpSocket->close();
    MYLOG<<"关闭";
}

// main.cpp
#include "tinydiskclient.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // TinyDiskClient w;
    // w.show();
    TinyDiskClient::getInstance().show();
    QObject::connect(&a, &QApplication::aboutToQuit, []() {
        qDebug() << "Application is about to quit!";
    });
    return a.exec();
}

开始面向AI编程

问题1:

方式1(普通对象创建):TinyDiskClient w; w.show();
方式2(单例模式):TinyDiskClient::getInstance().show();
询问两种方式在资源释放的区别:
给到的答案有:

方面方式1(普通对象创建)方式2(单例模式)
对象生命周期在这种方式下,TinyDiskClient 对象 w 是一个栈上对象,其生命周期由其所在的作用域决定。当作用域结束(例如函数 main 结束或离开当前代码块时),对象 w 会自动被销毁,调用它的析构函数,从而释放所有资源。在这种方式下,TinyDiskClient 是以单例模式创建的。单例对象通常是一个静态局部变量或静态指针,在第一次调用 getInstance() 时创建,并在程序的整个生命周期内持续存在。
资源清理由于 w 是一个栈上对象,它会在作用域结束时自动调用析构函数,自动清理资源。你不需要手动管理它的销毁。因为单例对象通常是静态的,所以它的资源清理并不会在常规作用域结束时自动发生。如果需要清理资源,你需要在程序退出之前明确地销毁单例对象(例如通过提供一个 destroyInstance() 方法,或使用智能指针如 std::unique_ptr 或 QScopedPointer 自动管理)。
实例数量每次调用都会创建一个新的 TinyDiskClient 实例。如果你多次使用这种方式,会有多个 TinyDiskClient 对象共存。单例模式确保 TinyDiskClient 只有一个实例。所有调用 getInstance() 的地方都会得到同一个实例,因此这可以避免多个实例带来的资源冲突或数据不一致问题。
对象销毁当 TinyDiskClient w 所在的作用域结束时,w 将自动调用析构函数,清理资源。通常不需要额外的资源清理代码。单例对象在程序结束时才会被销毁。因为它是静态的,除非显式调用某种销毁方法,否则它的资源清理会延迟到程序完全结束时(或者根本不发生,取决于实现)。

所以总的来说:就是方式1应该是自动销毁了,方式2得等到整个程序结束才能销毁。

为了验证以上说法:在TinyDiskClient 的析构函数添加如下语句打印出QApplication的对象指针,从结果可以看出,单例模式的周期比QApplication的周期还要长。

MYLOG<<" (Q_UNLIKELY(QCoreApplication::instance()):"<<(Q_UNLIKELY(QCoreApplication::instance()));
方式1(普通对象创建)的输出方式2(单例模式)的输出
(Q_UNLIKELY(QCoreApplication::instance()): QApplication(0xeae711fd20)(Q_UNLIKELY(QCoreApplication::instance()): QObject(0x0)
问题2:QApplication 和 TinyDiskClient 的关系?
  1. QApplication 是一个管理 GUI 应用程序的核心对象。它处理应用程序的初始化、事件循环和与操作系统的交互。通常,QApplication 对象是在 main() 函数中创建的,它的生命周期贯穿整个应用程序的运行时间。

#include <QApplication>
#include "tinydiskclient.h"

int main(int argc, char *argv[])
{
	// 创建方式
    QApplication app(argc, argv);

    TinyDiskClient &client = TinyDiskClient::getInstance();
    client.show();

    return app.exec();
}

  1. TinyDiskClient 是一个自定义的窗口类,它继承自 QWidget。
  2. 二者关系: 对于普通创建方式来说:TinyDiskClient 作为 QApplication 的一个窗口,在 QApplication 的事件循环中运行,且其生命周期由 QApplication 控制。

结论

  1. 使用单例模式确实可以获取类的唯一实例,但是使用单例模式来调用窗口类不太合适,其生命周期超过了QApplication,在这个程序中才会出现 QPixmap: Must construct a QGuiApplication before a QPixmap这种错误。
  2. 为了实现在整个程序调用TcpSocket,所以采取以下解决方案:声明一个类继承QTcpScoket,并使用单例模式,获取唯一实例。

以上均为个人在学习中的看法,若有错误,请不吝赐教~

  • 18
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值