我最近在处理一个 C++ 程序时遇到了问题。具体来说,我一直在开发一个使用 Qt 的 GUI 框架的程序,并且遇到了似乎与指针的双重删除和异常处理有关的错误。问题是我觉得我正在使用的 API 以一种不完全可预测的方式工作,正因为如此,我遇到了很多似乎违反直觉的错误.我不是世界上最有经验的 C++ 程序员,所以也许我缺少一些使用新 API 的总体策略。
这是一个例子:我通常总是尝试删除我在同一个类中动态分配的对象。换句话说,如果我在类的构造函数或 init 函数中使用 new 关键字填充指针,那么我通常会确保 delete类的析构函数。
这是给我带来问题的类 [MyProject.h] 的类定义的简化示例:
#ifndef MYPROJECT_H
#define MYPROJECT_H
#include "QObject.h"
class QGuiApplication;
class QQmlApplicationEngine;
#define MYPROJECT MyProject::getInstance()
class MyProject : public QObject
{
Q_OBJECT
private:
explicit MyProject(QObject *parent = 0); //singleton..
MyProject(MyProject const&); //uncopyable..
void operator=(MyProject const&); //unassignable..
QGuiApplication * QtGUI;
QQmlApplicationEngine * QmlAppEngine;
public:
~MyProject(void);
/* Globally available function to get MyProject's singleton instance.
* You can use the "MYPROJECT" preprocessor macro for shorthand. */
static MyProject & getInstance(void)
{
static MyProject instance;
return instance;
}
int init(int argc, char * argv[]);
int exec(void);
signals:
public slots:
};
#endif
这是我简化后的 main.cpp 的样子:
#include "MyProject.h"
int main(int argc, char * argv[])
{
MYPROJECT.init(argc, argv);
return MYPROJECT.exec();
}
这是我最初为该类 [MyProject.cpp] 使用的 ctor 和 init():
MyProject::MyProject(QObject *parent) :
QObject(parent) ,
QtGUI(NULL) ,
QmlAppEngine(NULL)
{
}
MyProject::~MyProject(void)
{
//segfault: debug points to both of these..
if (QtGUI) delete QtGUI;
if (QmlAppEngine) delete QmlAppEngine;
}
int MyProject::init(int argc, char * argv[])
{
QtGUI = new QGuiApplication(argc, argv);
QmlAppEngine = new QQmlApplicationEngine();
if(QtGUI && QmlAppEngine)
{
//segfault: debug points to this..
QmlAppEngine->load(QUrl( QStringLiteral("qrc:///MyProject.qml") ));
}
else return 1;
}
int MyProject::exec(void)
{
return QtGUI->exec();
}
因此,我的计划是:ctor 将指针初始化为 NULL,init() 用新对象填充这些指针,如果这些指针不为空,dtor 会使用 delete 清除它们。但是这段代码因 2 个段错误而崩溃,但即使我认为我已经缩小了它的范围,我不确定我是否理解为什么它们都会发生。
(1) 段错误 #1 是启动时的崩溃,它指向 init() 中的“QmlAppEngine->load()”调用。通过将函数调用包装在如下异常处理代码中,我能够防止崩溃的发生:
int MyProject::init(int argc, char * argv[])
{
QtGUI = new QGuiApplication(argc, argv);
QmlAppEngine = new QQmlApplicationEngine();
if(QtGUI && QmlAppEngine)
{
//exception handling prevents crash..
try
{
QmlAppEngine->load(QUrl( QStringLiteral("qrc:///MyProject.qml") ));
}
catch(int e)
{
std::cout << "Exception: " << e << std::endl;
return 1;
}
return 0;
}
else return 1;
}
我对异常处理不是很熟悉,因为到目前为止我编写的大部分代码都使用了 int 返回代码样式的错误处理。我猜 load 函数在某些情况下会抛出异常,不处理它们会导致崩溃。一旦我做了这个改变,程序就停止在启动时崩溃,但奇怪的是,它似乎并没有真正抛出异常,因为我的“cout”从不输出任何东西。我不明白的是这段代码是在 Qt Creator 创建的全新 Qt 项目的默认设置中调用时没有任何异常处理代码 - 例如,这是您在 QtCreator IDE 中启动新 QtQuick2 项目时看到的内容:
#include "QGuiApplication"
#include "QQmlApplicationEngine"
int main(int argc, char * argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine();
//default Qt file calls this without issue though..
engine.load(QUrl( QStringLiteral("qrc:///MyQml.qml") );
return app.exec();
}
我在这里看到的唯一主要不同是默认代码使用对象而不是指向对象的指针。但是,在这种情况下,负载运行良好,没有异常处理代码,也没有段错误。
(2) 下一个问题是当我的 dtor 在这两个指针上调用 delete 关键字时引起的。如果我注释掉这两行,程序运行良好并在没有崩溃或问题的情况下关闭。这使我相信 API 已使这些对象稍后自行删除,当我还显式调用 delete 时,由于双重删除而导致段错误。但是,一般来说,如何知道他们使用的 API 是否在内部处理对象删除?而且,如果我无法判断 API 指定对象是否被自动删除,我是否应该采取任何额外措施(即:使用某种智能指针等)?通常我假设我应该删除同一个类的析构函数中的任何动态分配的对象,但显然在这种情况下这会适得其反。
那么我可以采取哪些步骤来处理我使用的 API,以 (a) 防止错误和 (b) 允许我确保正确释放资源并处理异常?
最佳答案
通过查看您提供的示例代码很难找到错误的确切位置,您的应用程序必须具有庞大的代码库并且需要执行许多内存操作。 Qt 是一个设计良好且文档齐全的框架(尽管有些文档具有误导性或已过时),如果您感到困惑,我建议您正确阅读有关特定项目的文档。以下是我想您在使用 Qt 时应该知道/考虑的一些一般问题:
- 在继承
QObject
的类的堆上创建对象时,如果在构造函数中传递父对象(另一个QObject
),则子对象是由父对象拥有,内存将由父对象自动释放。 QObject
是NO_COPYABLE
,因此如果您从它继承,则不需要将copy ctor/assignment operator
设为私有(private)。编译器生成的这些方法的版本调用父版本(此处为QObject
),因此您的类自动不可复制/可分配。new
在失败时默认抛出bad_alloc
异常,而不是返回NULL
。因此,要么使用try-catch
,要么使用new
的no_throw
版本更改默认行为(new(std::nothrow)
), 失败时返回nullptr
。delete
NULL
指针不会导致任何问题。但是,如果pointer
指向任意位置/包含垃圾值,delete
ing 它将导致segfault
。- 默认情况下,
engine.load
不与异常处理程序 一起使用,因此很有可能不会引发异常。仔细查看代码的其他区域。
关于c++ - 解决由于库的意外行为导致的段错误和其他问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23600109/