Should I use QScopedPointer or std::unique_ptr?

后端 未结 3 2201
后悔当初
后悔当初 2021-02-19 10:59

I\'m starting a new project, using Qt5 and QMAKE_CXXFLAGS += -std=c++1y. I\'m not sure whether I should prefer QScopedPointer or std::unique_ptr

3条回答
  •  没有蜡笔的小新
    2021-02-19 11:13

    QScopedPointer is strictly weaker than unique_ptr as it does not support move semantics.

    Its functionality is otherwise extremely similar.

    Move semantics are extremely useful, and accidentally using them incorrectly to cause problems is extremely rare. So they vary from harmless to (more typically) helpful.

    About the only reason you should use QScopedPointer is interoperability with existing code bases; and even there, given how similar they are, an adapter would be pretty easy.

    So if you don't need to adapt, use unique_ptr.


    I will now discuss adapting.

    The tricky part is the 2nd parameter to QScopedPointer. It very roughly corresponds to the 2nd parameter of unique_ptr.

    In unique_ptr stateful deleters are permitted. In QScopedPointer they are not. The

    static void cleanup(T* pointer)
    

    corresponds to the

    void operator()(T* pointer)const
    

    in the unique_ptr in a pretty one-to-one basis. So:

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

    maps a Qt deleter to a std deleter. The other way is limited by the deleter being stateless:

    template
    struct Qt_deleter {
      template
      static void cleanup(T* target) {
        static_assert(std::is_empty{}, "Only works with stateless deleters");
        Std_deleter{}(target);
      }
    };
    

    we can now convert:

    template
    QScopedPointer>
    to_qt( std::unique_ptr&& src ) {
      return src.release();
    }
    template
    QScopedPointer>
    to_qt( std::unique_ptr&& src ) {
      return src.release();
    }
    template
    QScopedPointer
    to_qt( std::unique_ptr&& src ) {
      return src.release();
    }
    template
    QScopedPointer
    to_qt( std::unique_ptr&& src ) {
      return src.release();
    }
    template<
      class T, class D, class R=std::unique_ptr >
    >
    to_std( QScopedPointer&& src ) {
      return R(src.take()); // must be explicit
    }
    template>
    to_std( QScopedPointer&& src ) {
      return R(src.take()); // must be explicit
    }
    template>
    to_std( QScopedPointer&& src ) {
      return R(src.take()); // must be explicit
    }
    

    which covers about the only reason why you'd use QScopedPointer. There are a few corner cases -- the default deleter QScopedPointer should be converted to a default std::unique_ptr and vice versa.

    The array delete QScopedPointer should be converted to a unique_ptr and vice versa.

    In other cases, I simply wrap up the deleter. In theory, a really fancy trick would be to notice if the incoming deleter was already wrapped up and reverse the wrapping, but if your code is doing that many round-trips there is probably already something wrong.

提交回复
热议问题