是否有一个C ++容器具有合理的随机访问权限,从不调用元素类型的复制构造函数?

时间:2010-08-19 00:22:37

标签: c++ stl

我需要一个实现以下API的容器(不需要实现其他任何东西):

class C<T> {
  C();

  T& operator[](int); // must have reasonably sane time constant

    // expand the container by default constructing elements in place.
  void resize(int); // only way anything is added.
  void clear();

  C<T>::iterator begin();
  C<T>::iterator end();
}

可用于:

class I {
 public:
  I();
 private:  // copy and assignment explicate disallowed
  I(I&);
  I& operator=(I&);
}

这种野兽的剂量存在吗?

vector<T>没有这样做(调整大小的动作),我不确定deque<T>有多快。


我不关心分配

有些人认为我无法复制的原因是内存分配问题。限制的原因是元素类型明确禁止复制,我无法改变它。


看起来我得到了答案:STL没有答案。但现在我想知道Why not?

7 个答案:

答案 0 :(得分:5)

如果无法复制元素并在其他地方手动管理内存,则可以使用指针容器(如std::vector<T*>)。

如果向量应该拥有元素,那么像std::vector< std::shared_ptr<T> >这样的东西可能更合适。

还有Boost Pointer Container library,它为指针的异常安全处理提供容器。

答案 1 :(得分:5)

我很确定这里的答案是相当强调的“不”。根据您的定义,resize()应该分配新存储并使用默认构造函数初始化,如果我正确读取它。然后,您将通过索引到集合并操纵引用来操纵对象,而不是“插入”到集合中。否则,您需要复制构造函数和赋值运算符。标准库中的所有容器都有此要求。

您可能希望使用boost::ptr_vector<T>之类的内容。由于您正在插入指针,因此您不必担心复制。这需要您动态分配所有对象。

答案 2 :(得分:4)

使用deque:表现很好。

标准说,“deque是大多数插入和删除发生在序列的开头或结尾时所选择的数据结构”(23.1.1)。在您的情况下,所有插入和删除都在最后进行,符合使用deque的标准。

http://www.gotw.ca/gotw/054.htm提供了一些关于如何衡量绩效的提示,尽管可能是您考虑了特定的用例,因此这就是您应该衡量的内容。

编辑:好的,如果您对deque的反对意见实际上不是,“我不确定deque有多快”,但“元素类型不能是标准容器中的元素“,那么我们可以排除任何标准容器。不,这样的野兽不存在。 deque“永远不会复制元素”,但它会从其他对象复制构造它们。

接下来最好的事情可能是创建元素数组,默认构造,并维护指向这些元素的指针容器。这些方面的东西,虽然这可能会大大调整。

template <typename T>
struct C {
    vector<shared_array<T> > blocks;
    vector<T*> elements; // lazy, to avoid needing deque-style iterators through the blocks.
    T &operator[](size_t idx) { return *elements[idx]; }
    void resize(size_t n) {
        if (n <= elements.size()) { /* exercise for the reader */ }
        else {
            boost::shared_array<T> newblock(new T[elements.size() - n]);
            blocks.push_back(newblock);
            size_t old = elements.size();
            // currently we "leak" newblock on an exception: see below
            elements.resize(n);
            for (int i = old; j < n; ++i) {
                elements[i] = &newblock[i - old];
            }
    }
    void clear() {
        blocks.clear();
        elements.clear();
    }
};

当您添加更多函数和运算符时,它将接近deque,但避免任何需要复制类型T的内容。

编辑:想到这一点,在有人做resize(10); resize(20); resize(15);的情况下,我的“读者练习”不能完全正确地完成。你不能半删除一个数组。因此,如果您想要正确地重现容器resize()语义,立即破坏多余的元素,那么您将不得不单独分配元素(或熟悉新的位置):

template <typename T>
struct C {
    deque<shared_ptr<T> > elements; // or boost::ptr_deque, or a vector.
    T &operator[](size_t idx) { return *elements[idx]; }
    void resize(size_t n) {
        size_t oldsize = elements.size();
        elements.resize(n);
        if (n > oldsize) {
            try {
                for (size_t i = oldsize; i < n; ++i) {
                    elements[i] = shared_ptr<T>(new T());
                }
            } catch(...) {
                // closest we can get to strong exception guarantee, since
                // by definition we can't do anything copy-and-swap-like
                elements.resize(oldsize);
                throw;
            }
        }
    }
    void clear() {
        elements.clear();
    }
};

更好的代码,不那么热衷于内存访问模式(但是,我不清楚性能是否是一个问题,因为你担心deque的速度。)

答案 3 :(得分:3)

正如您所发现的,所有标准容器都与您的要求不符。如果我们可以做出一些额外的假设,那么编写自己的容器就不会太难了。

  • 容器将永远增长 - resize将始终以比以前更大的数量被调用,永远不会更少。
  • resize可以使容器大于要求的容器;在容器末端构造一些未使用的对象是可以接受的。

这是一个开始。我把很多细节留给你。

class C<T> { 
  C();
  ~C() { clear(); }

  T& operator[](int i) // must have reasonably sane time constant 
  {
      return blocks[i / block_size][i % block_size];
  }

    // expand the container by default constructing elements in place. 
  void resize(int n) // only way anything is added. 
  {
      for (int i = (current_size/block_size)+1; i <= n/block_size;  ++i)
      {
          blocks.push_back(new T[block_size]);
      }
      current_size = n;
  }

  void clear()
  {
      for (vector<T*>::iterator i = blocks.begin();  i != blocks.end();  ++i)
          delete[] *i;
      current_size = 0;
  }

  C<T>::iterator begin(); 
  C<T>::iterator end(); 
private:
  vector<T*> blocks;
  int current_size;
  const int block_size = 1024; // choose a size appropriate to T
} 

P.S。如果有人问您为什么要这样做,请告诉他们您需要一个std::auto_ptr数组。那应该是笑的好。

答案 4 :(得分:2)

所有标准容器都需要可复制元素。至少因为push_back和insert复制传递给它们的元素。我不认为你可以逃脱std :: deque,因为即使它的resize方法也需要复制参数来填充元素。

要在标准容器中使用完全不可复制的类,您必须存储指向这些对象的指针。这有时可能是一种负担,但使用shared_ptr或各种boost pointer containers可以使其变得更容易。

如果你不喜欢任何这些解决方案,那么请浏览其余的提升。也许还有其他适合的东西。也许是intrusive containers

否则,如果您认为任何这些都不符合您的需求,那么您可以随时尝试使用您自己的容器来完成您想要的任务。 (或者做更多的搜索,看看是否有其他人做过这样的事情。)

答案 5 :(得分:1)

您不应该根据它处理内存的方式来选择容器。例如deque是一个双端队列,所以你只能在需要双端队列时使用它。

如果调整大小,每个容器都会分配内存!当然,您可以通过调用vector::reserve来预先更改容量。容量是内存中物理元素的数量,大小是您正在使用的数量。

显然,如果你超过你的容量,仍会有分配。

答案 6 :(得分:0)

看看::boost::array。它不允许在创建容器后调整容器大小,但它不会复制任何东西。

同时获得resize和不复制都是一招。我不相信::std::deque因为我认为它可能会在某些情况下复制。如果你真的需要调整大小,我会编写你自己的deque-like容器。因为您要调整大小并且不进行复制的唯一方法是使用::std::deque之类的页面系统。

此外,拥有一个页面系统必然意味着at不会像::std::vector::boost::array那样快速地与其连续的内存布局一样快,即使它仍然可以相当快。