QVector为什么要求默认构造函数?

7
我可以看出类被视为复杂对象,需要调用默认构造函数:
void QVector<T>::defaultConstruct(T *from, T *to)
{
    if (QTypeInfo<T>::isComplex) {
        while (from != to) {
            new (from++) T();
        }
    ...
}

但是为什么需要在QVector的“隐藏”区域构建对象并不清楚。我的意思是这些对象根本无法访问,那么为什么不仅保留内存而不是真正的对象创建呢?

作为一个额外的问题,我想问一下,如果我想要一个非默认构造对象的数组,我是否可以安全地用 QVector<Wrapper<T>替换 QVector<T>?其中Wrapper类似于以下内容:

class Wrapper {
public:
    union {
        T object;
        bool hack;
    };
    Wrapper() {}
    Wrapper(const T &t) : object { t }  {}
    Wrapper(const Wrapper &t) : object { t.object } {}

    Wrapper &operator=(const Wrapper &value) {
        object = value.object;
        return *this;
    }

    ~Wrapper() {}
};

4
й»ҳи®Өжһ„йҖ еҮҪж•°иў«QVector(int)е’Ңresize(int)з”ЁдәҺе®һйҷ…еңЁеҗ‘йҮҸдёӯзҡ„е…ғзҙ гҖӮ - aschepler
1
@NeilKirk 我并没有看到使用std::unique_ptr相比这个hack有任何优势,而且磨损内存的巨大缺点会抵消QVector本身的优势。此外,请不要忘记,std::vector没有默认构造函数的要求。 - Grief
一个优点是它不使用未定义的行为。你可以使用放置 new 来代替。 - Neil Kirk
1
不,安置新对象让你把你的对象放在你选择的缓冲区中。它可以是你的包装器成员。如果你尝试使用你的对象 - 包括将一个新对象复制到其中 - 当它使用你的hack创建时使用“默认构造函数”,那么这是未定义的行为。 - Neil Kirk
1
T QVector::value(int i) const 也需要一个默认构造函数,因为如果索引超出范围,你会得到一个默认值。 - ymoreau
显示剩余7条评论
1个回答

0

对于非默认构造类型 T,使 QVector 正常工作相当容易:

#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); }

宏需要在MyType声明之后立即出现 - 在头文件(如果有的话)中,并且必须在命名空间或全局作用域中:

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

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

不,这个包装器是不正确的。它没有析构对象成员。它也没有提供移动语义,不能保护免受默认构造等方面的影响。hack联合成员是不必要的。联合中的任何内容都不会被自动默认构造。
这是一个更正确的包装器 - 它非常类似于std :: optional。请参见此处,了解optional需要多少微妙的内容 :)
// 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();
   }
};

由于赋值运算符是 ref-qualified 的,它不允许对 rvalue 进行赋值,因此具有 IMHO 正面属性,以下内容将无法编译:

Wrapper<int>() = 1   // likely Wrapper<int>() == 1 was intended

detach()确保容器不与任何其他实例共享数据:如果是这样,则会进行深复制。 - Kuba hasn't forgotten Monica
但是如果我理解正确的话,resize函数实际上不会调整任何大小。这也意味着clear()不会清除容器,因为如果你查看源代码,clear()会调用resize(0) - Donald Duck
没错。就我现在所记得的,这是一种必要的限制,以便QVector支持这些类型:它不能保留所有功能。这是这个类设计的缺陷。我想不久之后,Qt的容器将被弃用并保留向后兼容性 - 我可能是错误的,但扩展其功能的更改不会被接受 - 目标是不将API推入某些难以控制的角落。值得检查是否已经做出了一些明确的路线决策。 - Kuba hasn't forgotten Monica

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