Qt在信号/槽中使用boost::shared_ptr

9
有没有可能在Qt中创建一个指向shared_ptr的const引用的信号/插槽?如果可以,那么如何实现呢?我想要这样一个信号:
void signal( shared_ptr const & )
我知道如何在没有常量引用的情况下实现,那就是使用类型shared_ptr,但出于效率原因,我希望避免复制。然而,对于引用类型,同样的语法不起作用:
Q_DECLARE_METATYPE(shared_ptr<SomeClass> const &)
qRegisterMetaType<shared_ptr<SomeClass> const&>();

许多标准API都有QString const &,因此我认为这在基本上是可能的,只是我无法弄清语法。
性能最大的问题不是复制时间,而是当对象被复制到每个接收器时需要进行的互斥锁定/解锁的数量--这些接收器很多。由于多个线程使用该对象,这会引入明显的减速/瓶颈。如果shared_ptr实际上只使用原子操作,那么这个成本也是微不足道的,但关于信号中const引用的一般问题仍然存在。

将 shared_ptr 以 const 引用方式传递是否否定了使用 shared_ptr 的初衷?直接传递指向相关对象的 const 引用 / 指针即可。 - messenger
1
@messenger,const引用是一种优化方式,可以避免在调用函数时复制shared_ptr。这是一个相当常见的模式。如果您总是通过值传递(信号或普通函数)shared_ptr,则与普通指针相比,它们会变得非常昂贵。普通指针不能在此处使用,因为“emit”端对象的生命周期无法保证。 - edA-qa mort-ora-y
@edA-qa mort-ora-y 只是想跟进 Sergey 的观点,我认为你没有理解。这里很明显的一点是:如果你引用了一个对象,但原始对象超出了其作用域,该引用就会变成无效的。不太明显的事情是:如果你不希望引用变得无效,那就意味着原始 shared_ptr 永远不会超出其作用域,在这种情况下,它首先就不需要是 shared_ptr? - messenger
@edA-qa,我同意messenger的观点。如果你传递一个shared_ptr的引用,但它的生命周期不能得到保证,那么shared_ptr能正常工作吗?我怀疑它不能,因为它不会增加引用计数。你只会得到一个无效的引用,指向shared_ptr曾经存在的内存。 - Sergei Tachenov
1
@edA-qa,哦,现在我明白了。你想要制作一个只需通过引用传递给每个连接的插槽的副本。现在这是一个好主意,但不幸的是Qt以不同的方式处理事情。我猜你可以通过首先将指针的副本传递给接收线程中的某个代理对象来解决它,该代理对象将使用直接连接信号所有接收器,但恐怕双重信号开销会打败避免不必要复制的目的。 - Sergei Tachenov
显示剩余9条评论
3个回答

8

到目前为止,我发现我可以简单地这样做:

Q_DECLARE_METATYPE(shared_ptr<SomeClass>)
qRegisterMetaType<shared_ptr<SomeClass> >();
qRegisterMetaType<shared_ptr<SomeClass> >("std::shared_ptr<SomeClass>const&");

我现在正在验证这个是否真正可以正确工作。这里的文档并没有清楚说明实际发生了什么。似乎信号/槽中的const引用类型只会被转换为普通的shared_ptr<SomeClass>,这在这里完全没问题。但是如果有一些保证可以让它按照这种方式工作会更好。
我有一种感觉,只需要简单的shared_ptr<SomeClass>版本,而是boost命名空间干扰了信号。第二个版本似乎只是在全局命名空间中注册信号以便更容易使用。
经过测试,我可以确认const &部分在排队连接中被完全忽略了。每个连接的槽都会得到一个新的对象副本。这非常不幸。 :(
进一步的测试表明,&被用于槽,但使用方式不同寻常。对象的副本仍然为排队连接创建,但如果您不使用引用,则会为调用创建另一个副本。
因此,尽管每个connect最终都会复制数据以进行排队连接,但引用仍然有所帮助。如果您有一些本地发送的信号(同一线程),您可能会避免更多的复制。

1
  1. 你应该能够完全省略qRegisterMetaType()中的字符串参数。
  2. 对于信号/槽和元类型等,const ref从未被需要,只需要普通类型即可。同时,const ref和按值传递对于信号/槽机制来说是等效的:您可以通过SIGNAL(signal(const T&))或SIGNAL(signal(T))连接一个void signal( const T& )信号,签名将被规范化为"signal(T)"。当进行排队连接时,该值通过复制而不是存储const ref来存储。
- Frank Osterfeld
@Frank,对于排队,我可以接受复制。我担心的是分派给接收器。如果我将许多插槽连接到具有引用的信号,那么在所有情况下(即同一线程中的所有插槽)是否都使用对同一对象的引用? - edA-qa mort-ora-y
1
QA,我刚刚检查了源代码,看起来Qt为每个排队的连接(即每个接收器)创建了一份副本。如果您愿意,请检查QMetaObject :: activate(),它循环遍历发送器和QMetaObject :: queued_activate(),这些都在qobject.cpp中实际复制。 - Sergei Tachenov
你必须在字符串内添加 std:: 命名空间,因为 using namespace 不会影响到它。 - Lord Zsolt

2

在同一线程中使用复制构造函数(或operator=)进行拷贝是完全可以的,但我不希望将拷贝传播到所有接收者。 - edA-qa mort-ora-y

0
为了完整起见,我认为应该提到Qt有自己的共享指针实现QSharedPointer,其行为与std::shared_ptr完全相同。不过最终你仍然需要注册你的参数类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接