个人经验总结,如有错误或遗漏,欢迎各位大佬指正 ?
@
众所周知,Qt中,有为对象设置父对象的方法——setParent。
而设置父对象的作用主要有,在父对象析构的时候,会自动去析构其子对象。如果是一个窗口对象,如果其父对象设置了样式表(Style Sheet),子对象也会继承父对象的样式。
所以,这篇文章,咱们主要看一下setParent的源码以及QObject是怎么进行对象管理的。
我们可以看到,setParent这个函数就是调用了QObjectPrivate类的setParent_helper这个函数。
void QObject::setParent(QObject *parent)
{
Q_D(QObject);
Q_ASSERT(!d->isWidget);
d->setParent_helper(parent);
}
所以,我们进一步分析setParent_helper这个函数
void QObjectPrivate::setParent_helper(QObject *o)
{
Q_Q(QObject);
// 不能把自己设为父对象
Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself");
#ifdef QT_DEBUG
// 检查对象树的循环
const auto checkForParentChildLoops = qScopeGuard([&](){
int depth = 0;
auto p = parent;
while (p) {
if (++depth == CheckForParentChildLoopsWarnDepth) {
qWarning("QObject %p (class: '%s', object name: '%s') may have a loop in its parent-child chain; "
"this is undefined behavior",
q, q->metaObject()->className(), qPrintable(q->objectName()));
}
p = p->parent();
}
});
#endif
// 如果要设置的父对象就是当前的父对象,直接返回
if (o == parent)
return;
if (parent) {
QObjectPrivate *parentD = parent->d_func();
if (parentD->isDeletingChildren && wasDeleted
&& parentD->currentChildBeingDeleted == q) {
// don't do anything since QObjectPrivate::deleteChildren() already
// cleared our entry in parentD->children.
} else {
const int index = parentD->children.indexOf(q);
if (index < 0) {
// we're probably recursing into setParent() from a ChildRemoved event, don't do anything
} else if (parentD->isDeletingChildren) {
parentD->children[index] = 0;
} else {
// 如果对象已经存在父对象的列表中,将原先存在的对象删除,并发送事件
parentD->children.removeAt(index);
if (sendChildEvents && parentD->receiveChildEvents) {
QChildEvent e(QEvent::ChildRemoved, q);
QCoreApplication::sendEvent(parent, &e);
}
}
}
}
// 设置父对象
parent = o;
if (parent) {
// object hierarchies are constrained to a single thread
if (threadData != parent->d_func()->threadData) {
qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");
parent = nullptr;
return;
}
// 父对象添加子对象,并发送事件
parent->d_func()->children.append(q);
if(sendChildEvents && parent->d_func()->receiveChildEvents) {
if (!isWidget) {
QChildEvent e(QEvent::ChildAdded, q);
QCoreApplication::sendEvent(parent, &e);
}
}
}
if (!wasDeleted && !isDeletingChildren && declarativeData && QAbstractDeclarativeData::parentChanged)
QAbstractDeclarativeData::parentChanged(declarativeData, q, o);
}
一些先决条件的判断
判断设置的父对象是否是自己
// 不能把自己设为父对象
Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself");
/*...*/
// 如果要设置的父对象就是当前的父对象,直接返回
if (o == parent) return;
判断原来的父对象是否处于正在删除子对象的过程中,并且当前对象已经被删除了,如果是,则什么都不做(有点迷惑)
if (parentD->isDeletingChildren && wasDeleted
&& parentD->currentChildBeingDeleted == q) {
// don't do anything since QObjectPrivate::deleteChildren()
//already cleared our entry in parentD->children.
}
判断是不是通过从ChildRemoved事件递归到setParent()
if (index < 0) {
// we're probably recursing into setParent() from a ChildRemoved event,
// don't do anything
} else if (parentD->isDeletingChildren) {
parentD->children[index] = 0;
}
判断对象是不是已存在父对象的列表中,如果存在,就将对象删除,并发送事件
else {
// 如果对象已经存在父对象的列表中,将原先存在的对象删除,并发送事件
parentD->children.removeAt(index);
if (sendChildEvents && parentD->receiveChildEvents) {
QChildEvent e(QEvent::ChildRemoved, q);
QCoreApplication::sendEvent(parent, &e);
}
}
设置父对象,这里有一个限制,就是新设置的父对象,必须和当前对象在同一个线程,否则不能设置。
// 设置父对象
parent = o;
if (parent) {
// object hierarchies are constrained to a single thread
if (threadData != parent->d_func()->threadData) {
qWarning("QObject::setParent: Cannot set parent, \
new parent is in a different thread");
parent = nullptr;
return;
}
// 父对象添加子对象,并发送事件
parent->d_func()->children.append(q);
if(sendChildEvents && parent->d_func()->receiveChildEvents) {
if (!isWidget) {
QChildEvent e(QEvent::ChildAdded, q);
QCoreApplication::sendEvent(parent, &e);
}
}
}
然后就是对象的管理,也就是在父对象析构的时候,自动析构掉所有的子对象。这一个在我们使用窗口部件的时候很有用,因为一个界面可能有很多个子控件,比如按钮、label等,这时候,如果一个小窗口被关闭,我们也不需要一个一个的去析构,由Qt的对象树去进行析构就好了。
QObject::~QObject()
{
/*...*/
// 删除子对象
if (!d->children.isEmpty())
d->deleteChildren();
#if QT_VERSION < 0x60000
qt_removeObject(this);
#endif
if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject]))
reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this);
Q_TRACE(QObject_dtor, this);
if (d->parent) // remove it from parent object
d->setParent_helper(nullptr);
}
将所有的子对象进行删除,遍历容器,按照子对象所加入进来的顺序进行析构。
void QObjectPrivate::deleteChildren()
{
// 清空子对象
Q_ASSERT_X(!isDeletingChildren, "QObjectPrivate::deleteChildren()", "isDeletingChildren already set, did this function recurse?");
isDeletingChildren = true;
// delete children objects
// don't use qDeleteAll as the destructor of the child might
// delete siblings
for (int i = 0; i < children.count(); ++i) {
currentChildBeingDeleted = children.at(i);
children[i] = 0;
delete currentChildBeingDeleted;
}
children.clear();
currentChildBeingDeleted = nullptr;
isDeletingChildren = false;
}
在使用Qt的对象树这个功能的时候,可能会遇到一种问题,会导致程序崩溃:就是手动的管理(也就是直接delete)一个有父对象的QObject,为什么会出现这样的情况呢,因为,你在delete子对象之后,并没有把这个对象从父对象的对象树里移除。在父对象进行析构的时候,还是会去遍历子对象容器,一个一个析构。这个时候,就会出现,一个对象指针被删除了两次,自然就会崩溃。
那么,如果非要自己管理这个对象,有什么办法呢?我们从对象树下手,有两种办法:
使用deleteLater
就是调用QObject对象的deleteLater函数,来实现删除。关于deleteLater的分析,可以看这个大佬的文章Qt 中 deleteLater() 函数的使用
QObject *object = new QObject();
QObject *m_child = new QObject(object);
// 需要手动删除的时候
m_child->deleteLater();
先将父对象设置为空,再直接delete
QObject *object = new QObject();
QObject *m_child = new QObject(object);
// 需要手动删除的时候
m_child->setParent(nullptr);
delete m_child;
m_child = nullptr;
先将父对象设置为空,再直接delete
QObject *object = new QObject();
QObject *m_child = new QObject(object);
// 需要手动删除的时候
m_child->setParent(nullptr);
delete m_child;
m_child = nullptr;
个人建议使用第一种方法,也就是调用deleteLater
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调
我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el