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

前端 未结 1 2080
甜味超标
甜味超标 2020-12-19 06:25

I can see that that classes are treated as complex objects which are required for calling default constructor:

void QVector::defaultConstruct(T *fro         


        
相关标签:
1条回答
  • 2020-12-19 06:36

    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
    
    0 讨论(0)
提交回复
热议问题