What is the reason of QVector's requirement for default constructor?

旧城冷巷雨未停 提交于 2019-11-29 10:31:48

It's easy enough to make the QVector work for a non-default-constructible type T:

#define QVECTOR_NON_DEFAULT_CONSTRUCTIBLE(Type) \
template <> QVector<Type>::QVector(int) = delete; \
template <> void QVector<Type>::resize(int newSize) { \
   Q_ASSERT(newSize <= size()); \
   detach(); \
} \
template <> void QVector<Type>::defaultConstruct(Type*, Type*) { Q_ASSERT(false); }

The macro needs to be present right after MyType declaration - in the header file (if any), and it must be in namespace or global scope:

struct MyType { ... };
QVECTOR_NON_DEFAULT_CONSTRUCTIBLE(MyType)

struct A {
  struct MyType2 { ... };
};
QVECTOR_NON_DEFAULT_CONSTRUCTIBLE(A::MyType2);

No, the wrapper is not correct. It doesn't destruct the object member. It also doesn't offer move semantics, doesn't protect from being default-constructed, etc. The hack union member is not necessary. Nothing in a union will be default-constructed for you.

Here's a more correct wrapper - it pretty much resembles std::optional. See here to see how much nuance an optional needs :)

// https://github.com/KubaO/stackoverflown/tree/master/questions/vector-nodefault-33380402

template <typename T> class Wrapper final {
   union {
      T object;
   };
   bool no_object = false;
   void cond_destruct() {
      if (!no_object)
         object.~T();
      no_object = true;
   }
public:
   Wrapper() : no_object(true) {}
   Wrapper(const Wrapper &o) : no_object(o.no_object) {
      if (!no_object)
         new (&object) T(o.object);
   }
   Wrapper(Wrapper &&o) : no_object(o.no_object) {
      if (!no_object)
         new (&object) T(std::move(o.object));
   }
   Wrapper(const T &o) : object(o) {}
   Wrapper(T &&o) : object(std::move(o)) {}
   template <class...Args> Wrapper(Args...args) : object(std::forward<Args>(args)...) {}
   template <class U, class...Args> Wrapper(std::initializer_list<U> init, Args...args) :
      object(init, std::forward<Args>(args)...) {}
   operator T&      () &      { assert(!no_object); return object; }
   operator T&&     () &&     { assert(!no_object); return std::move(object); }
   operator T const&() const& { assert(!no_object); return object; }
   Wrapper &operator=(const Wrapper &o) & {
      if (no_object)
         ::new (&object) T(o);
      else
         object = o.object;
      no_object = false;
      return *this;
   }
   Wrapper &operator=(Wrapper &&o) & {
      if (no_object)
         ::new (&object) T(std::move(o.object));
      else
         object = std::move(o.object);
      no_object = false;
      return *this;
   }
   template<class... Args> T &emplace(Args&&... args) {
      cond_destruct();
      ::new (&object) T(std::forward<Args>(args)...);
      no_object = false;
      return object;
   }
   ~Wrapper() {
      cond_destruct();
   }
};

Since the assignment operators are ref-qualified, it disallows assigning to rvalues, so it has the IMHO positive property that the following won't compile:

Wrapper<int>() = 1   // likely Wrapper<int>() == 1 was intended
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!