【问题标题】:What is the reason of QVector's requirement for default constructor?QVector 要求默认构造函数的原因是什么?
【发布时间】:2016-01-27 14:20:58
【问题描述】:

我可以看到这些类被视为调用默认构造函数所需的复杂对象:

void QVector<T>::defaultConstruct(T *from, T *to)
{
    if (QTypeInfo<T>::isComplex) {
        while (from != to) {
            new (from++) T();
        }
    ...
}

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

作为一个额外的问题,我想问一下,如果我想要一组非默认可收缩对象,我可以安全地将QVector&lt;T&gt; 替换为QVector&lt;Wrapper&lt;T&gt; 吗?其中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() {}
};

【问题讨论】:

  • 如果一个对象没有默认构造函数,通常是有原因的。我建议不要黑客攻击。也许尝试将std::unique_ptr 存储在您的包装器中。
  • 默认构造函数被QVector(int)resize(int) 用于向量中实际存在的元素。
  • @NeilKirk 我没有看到使用std::unique_ptr 比这个hack 有任何优势,而在磨削内存方面有一个巨大的劣势,这将使QVector 本身的优势化为乌有。另外,请不要忘记,std::vector 不需要具有默认构造函数。
  • 不,placement new 允许您将对象放入您选择的缓冲区中。它可能是您的包装器的成员。如果您尝试使用您的对象 - 包括将新对象复制到其中 - 当它使用“默认构造函数”创建时,由于您的 hack,这是未定义的行为。
  • T QVector::value(int i) const 还需要一个默认构造函数,因为如果索引超出范围,您将获得一个默认值。

标签: c++ qt containers qvector


【解决方案1】:

QVector 为非默认可构造类型 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); }

宏需要出现在 MyType 声明之后 - 在头文件(如果有)中,并且必须在命名空间或全局范围内:

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

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

不,包装器不正确。它不会破坏 object 成员。它也不提供移动语义,不保护默认构造等。hack 联合成员不是必需的。联合中的任何内容都不会为您默认构建。

这是一个更正确的包装器 - 它非常类似于 std::optional。请参阅here 了解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,它不允许分配给右值,因此它具有以下不会编译的 IMHO 正属性:

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

【讨论】:

  • detach() 确保容器不与任何其他实例共享数据:如果共享,则进行深层复制。
  • 但是如果我理解正确的话,resize 函数实际上不会调整任何东西的大小。这也意味着clear() 不会清除容器,因为如果您查看源代码,clear() 会调用resize(0)
  • 没错。据我现在所记得,让QVector 支持这些类型是必要的限制:它不会使所有功能保持完整。这是这个类的设计的一个缺点。我想在不久的将来,Qt 的容器将被弃用并保留以实现向后兼容性——我可能错了,但不接受扩展其功能的更改——目标是不要将 API 退回到某个笨拙的角落。值得检查是否在这方面做出了一些坚定的路线图决定。
猜你喜欢
  • 2019-06-12
  • 2019-03-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-06
  • 2013-03-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多