问题描述
这两天在学习使用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 的关系?
- 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();
}
- TinyDiskClient 是一个自定义的窗口类,它继承自 QWidget。
- 二者关系: 对于普通创建方式来说:TinyDiskClient 作为 QApplication 的一个窗口,在 QApplication 的事件循环中运行,且其生命周期由 QApplication 控制。
结论
- 使用单例模式确实可以获取类的唯一实例,但是使用单例模式来调用窗口类不太合适,其生命周期超过了QApplication,在这个程序中才会出现
QPixmap: Must construct a QGuiApplication before a QPixmap
这种错误。 - 为了实现在整个程序调用TcpSocket,所以采取以下解决方案:声明一个类继承QTcpScoket,并使用单例模式,获取唯一实例。
以上均为个人在学习中的看法,若有错误,请不吝赐教~