我应该使用QScopedPointer还是std::unique_ptr?

15

我正在启动一个新项目,使用Qt5和QMAKE_CXXFLAGS += -std=c++1y。我不确定是否应该使用QScopedPointer还是std::unique_ptr

我在某个地方读到QScopedPointer已经不那么流行了。

QScopedPointer是否具有unique_ptr所缺少的任何功能?替换QScopedPointer时,unique_ptr是否存在我不想要的任何功能?还是反之亦然?


6
我会使用 std::unique_ptr - NathanOliver
我可以说我已经减少了对Qt智能指针的使用,转而使用std::shared_ptrstd::unique_ptr - drescherjm
3
每当我需要选择标准库或其他库中的类时,除非有强制性原因,我总是选择标准库。强制性原因可能是您需要与使用其他类型的代码交互操作。 - Mark Ransom
3
我添加了一些客观问题,以帮助做出决策。因此,现在不仅仅是一个意见,而是询问一个产品缺少的特点和另一个产品具备的特点。 - Yakk - Adam Nevraumont
我在某个地方读到,QScopedPointer 不再那么酷了。那个“某个地方”实际上是这个问题的重复。唉。 - jonspaceharper
3个回答

21

QScopedPointerunique_ptr弱,因为它不支持移动语义。

除此之外,它们的功能非常相似。

移动语义非常有用,而意外地不正确使用它们来引起问题非常罕见。因此,它们从无害到(更常见的是)有帮助的程度各不相同。

您应该使用QScopedPointer的唯一原因是与现有代码库的互操作性;即使在那里,考虑到它们的相似性,一个适配器也会非常容易。

因此,如果不需要适配,请使用unique_ptr


现在我将讨论适配。

棘手的部分是QScopedPointer的第二个参数。它大致对应于unique_ptr的第二个参数。

unique_ptr中,允许有状态删除器。在QScopedPointer中,它们不允许。

static void cleanup(T* pointer)

对应于

void operator()(T* pointer)const

unique_ptr以一对一的方式进行操作。因此:

template<class QDelete>
struct std_deleter {
  template<class T>
  void operator()(T* target) const {
    QDelete::cleanup(target);
  }
};

将Qt删除器映射到std删除器。另一种方法受限于删除器是无状态的:
template<class Std_deleter>
struct Qt_deleter {
  template<class T>
  static void cleanup(T* target) {
    static_assert(std::is_empty<Std_deleter>{}, "Only works with stateless deleters");
    Std_deleter{}(target);
  }
};

我们现在可以转换:
template<class T, class D>
QScopedPointer<T, Qt_deleter<D>>
to_qt( std::unique_ptr<T, D>&& src ) {
  return src.release();
}
template<class T, class D>
QScopedPointer<T, Qt_deleter<D>>
to_qt( std::unique_ptr<T[], D>&& src ) {
  return src.release();
}
template<class T>
QScopedPointer<T>
to_qt( std::unique_ptr<T>&& src ) {
  return src.release();
}
template<class T>
QScopedPointer<T, QScopedPointerArrayDeleter>
to_qt( std::unique_ptr<T[]>&& src ) {
  return src.release();
}
template<
  class T, class D, class R=std::unique_ptr<T, std_deleter<D> >
>
to_std( QScopedPointer<T, D>&& src ) {
  return R(src.take()); // must be explicit
}
template<class T, class R=std::unique_ptr<T>>
to_std( QScopedPointer<T>&& src ) {
  return R(src.take()); // must be explicit
}
template<class T, class R=std::unique_ptr<T[]>>
to_std( QScopedPointer<T,QScopedPointerArrayDeleter >&& src ) {
  return R(src.take()); // must be explicit
}

这段内容讲述了使用 QScopedPointer 的唯一原因。有一些特殊情况--默认删除器 QScopedPointer 应该转换为默认的 std::unique_ptr,反之亦然。

数组删除器 QScopedPointer 应该转换为 unique_ptr<T[]>,反之亦然。

在其他情况下,我只是简单地包装删除器。理论上,一个非常高级的技巧是注意到传入的删除器是否已经被包装,然后反向包装,但如果您的代码需要做那么多往返操作,那么可能已经出现了某些问题。


6
“与现有代码库的互操作性”我不会称其为“互操作性”。QScopedPointer 真正适用于 C++98 代码库(这也是 QScopedPointer 没有移动语义的原因)。考虑到你甚至无法移动它,它通常只是一种实现帮助工具,并且你不会在任何 API 中看到它的使用(不像 unique_ptr)。Qt 项目有意不使 QScopedPointer 变得更好 - 你已经有了那个,就是 unique_ptr。在新的、启用了 C++11 的项目中,请使用 unique_ptr 而不是 QScopedPointer - peppe
@peppe 想象一下一个有1000万行代码的代码库,它通过引用“返回”QScopedPointer,并且在各个地方都使用QScopedPointer ref和cref作为参数,其中一些是你不再拥有源代码但必须保留功能的遗留库,这真是令人颤抖。我的观点是,即使你有那个可恶的地狱般的现有代码库,也不要使用QScopedPointer。使用to_qtto_std - Yakk - Adam Nevraumont
@drumm "更改分辨率:此功能已被删除。" "超出范围" - 幼稚的理解是没有完成? - Yakk - Adam Nevraumont
@Yakk,确实是这样。可能是因为peppe所说的原因。再次不使用QScopedPointer的另一个原因。我也不能使用make_unique,因为编译器限制,但好在Qt正在适应标准。令人困惑的是,例如QSharedPointer具有移动语义,但我猜最好还是使用sharedpointer。 - jaques-sam
“QScopedPointer不支持移动语义”是什么意思?它没有移动构造函数和移动赋值运算符吗? - KcFnMi
@KcFnMi https://interest.qt-project.narkive.com/fWYzJaN9/qscopedpointer-and-c-11-move-semantics 可能已经有所改变,但至少在我个人检查的最后一个版本中,它不支持移动构造函数或移动赋值。 - Yakk - Adam Nevraumont

4

为什么会使用非标准库的东西而不是标准库中的东西呢?

对我来说,只有一个好程序员会这样做的原因:如果外部库提供了标准库没有提供的功能。这是这种情况吗?

考虑您的程序的可移植性和未来的更新,然后做出决定。


2

尽管这两个类的主要意图相似,但它们之间有一个重要的区别,这使您需要做出某些业务决策。

QScopedPointer没有赋值运算符。而std::unique_ptr有赋值运算符。

如果您想在QT对象/控件上严格执行并且不想使用赋值,请使用QScopedPointer。


5
const unique_ptr 同样没有赋值运算符。 - Howard Hinnant
1
是的。但是OP没有说const。 - Pavan Chandaka

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