tags:

views:

246

answers:

7

I need a container that implements the following API (and need not implement anything else):

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

and can be used on:

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

Dose such a beast exist?

vector<T> doesn't do it (resize moves) and I'm not sure how fast deque<T> is.


I don't care about allocation

Several people have assumed that the reason I can't do copies is memory allocation issues. The reason the constraints is that the element type explicitly disallows copying and I can't change that.


Looks like I've got my answer: SLTL doesn't have one. But now I'm wondering Why not?

+4  A: 

Use deque: performance is fine.

The standard says, "deque is the data structure of choice when most insertions and deletions take place at the beginning or at the end of the sequence" (23.1.1). In your case, all insertions and deletions take place at the end, satisfying the criterion for using deque.

http://www.gotw.ca/gotw/054.htm has some hints on how you might measure performance, although presumably you have a particular use-case in mind, so that's what you should be measuring.

Edit: OK, if your objection to deque is in fact not, "I'm not sure how fast deque is", but "the element type cannot be an element in a standard container", then we can rule out any standard container. No, such a beast does not exist. deque "never copies elements", but it does copy-construct them from other objects.

Next best thing is probably to create arrays of elements, default-constructed, and maintain a container of pointers to those elements. Something along these lines, although this can probably be tweaked considerably.

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

As you add more functions and operators, it will approach deque, but avoiding anything that requires copying of the type T.

Edit: come to think of it, my "exercise for the reader" can't be done quite correctly in cases where someone does resize(10); resize(20); resize(15);. You can't half-delete an array. So if you want to correctly reproduce container resize() semantics, destructing the excess elements immediately, then you will have to allocate the elements individually (or get acquainted with placement new):

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

Nicer code, not so keen on the memory access patterns (but then, I'm not clear whether performance is a concern or not since you were worried about the speed of deque.)

Steve Jessop
According to my expereince " deque: performance is fine." as long as you don't subtract two iterators.
SigTerm
Doesn't work, push_back and resize both copy.
BCS
They do for `vector` too. Unfortunately, 23/3: "The type of objects stored in [standard containers] must meet the requirements of CopyConstructible types and the additional requirements of Assignable types". So standard containers are out if it's a requirement that the value type be non-copyable: you'll have to roll your own, or use something like a container of pointers to elements of a default-initialized array.
Steve Jessop
I never need to insert elements. Any time the container is expanded, the new elements must be default constructed.
BCS
`resize` is defined in terms of `insert`. When you resize, you are inserting, whether you choose to call it that or not. Your type must be copyable because `resize()` takes its second (default) parameter by value. Even if you don't specify it, the compiler still needs to copy the default parameter value, and will copy-construct the new elements.
Steve Jessop
I guess my case is "the element type cannot do X, and X happens to be used by all standard containers". It really seems pointless to me. With placement new it *could* avoid using the copy constructor.
BCS
Well, I'm not here to persuade you that Stepanov knew what he was doing (although I happen to think he did, for the most part). Why not sketch out the interface for your NonCopyingSequence concept, compare it to the Container and Sequence concepts, and see how you think it would fit into the standard libraries? This is about library design and interface definitions: if one function can happen to avoid using a particular restriction that's fine, but it doesn't help unless it allows you to define a smaller container-like interface which is of general use.
Steve Jessop
IMVHO, container interfaces/concepts should be defined-as/result-from the intersection on available API's. If a container can offer more or require less than the concept it should and concepts should be defined to follow the implementation rather than the reverse. (But I could be wrong.)
BCS
But the *container* cannot require less in this example, since it has other functions which copy. One specific function of the container could require less. That's no help unless all functions document their individual requirements, because there would be no guarantee that some other thing you're using doesn't require the same feature that you're trying to avoid (in this case copying). Concepts cannot possibly be defined to follow "the" implementation, because the whole point of a standard is to allow me to write code which uses this interface, without caring which implementation I get.
Steve Jessop
Oh, sure it can require less. As it is, it demands a copyable type to do anything but create an empty instance. It could require a copyable type if and only if you want to insert into it. IIRC C++ instantiates methods of template classes lazily so unless you write code that could call a method, said method it is never generated or even type checked.
BCS
+1  A: 

You shouldn't pick a container based on how it handles memory. deque for example is a double-ended queue, so you should only use it when you need a double-ended queue.

Pretty much every container will allocate memory if you resize it! Of course, you could change the capacity up front by calling vector::reserve. The capacity is the number of physical elements in memory, the size is how many you are actively using.

Obviously, there will still be an allocation if you grow past your capacity.

EboMike
If you want to maintain pointers or references to the elements of the structure, then you have no choice but to base your choice on how it handles memory. `deque`, `set` and `map` avoid moving their contents.
Steve Jessop
I have no issues with allocation as long is the default constructor is used to fill it rather than the copy constructor or assignment operator.
BCS
@Steve, in that case, you should probably use a `vector<T*>`.
Ken Bloom
@BCS: no, value types of standard containers have to be copyable. Even `vector::resize()` copy-constructs new elements from a default-constructed parameter, it doesn't default-construct them. This is because `resize` has a default parameter, it isn't overloaded for 1- and 2-parameter versions.
Steve Jessop
+5  A: 

You could use a container of pointers, like std::vector<T*>, if the elements cannot be copied and their memory is managed manually elsewhere.

If the vector should own the elements, something like std::vector< std::shared_ptr<T> > could be more appropriate.

And there is also the Boost Pointer Container library, which provides containers for exception safe handling of pointers.

sth
Doesn't work as resize won't populate the new elements automatically.
BCS
@BCS: Couldn't you just write a function that resizes and creates the elements, and then use that function?
sth
that would be rather,... messy.
BCS
+2  A: 

All the standard containers require copyable elements. At the very least because push_back and insert copy the element passed to them. I don't think you can get away with std::deque because even its resize method takes parameter to be copied for filling the elements.

To use a completely non-copyable class in the standard containers, you would have to store pointers to those objects. That can sometimes be a burden but usage of shared_ptr or the various boost pointer containers can make it easier.

If you don't like any of those solutions then take a browse through the rest of boost. Maybe there's something else suitable in there. Perhaps intrusive containers?

Otherwise, if you don't think any of that suits your needs then you could always try to roll your own container that does what you want. (Or else do more searching to see if anyone else has ever made such a thing.)

TheUndeadFish
+4  A: 

I'm pretty sure that the answer here is a rather emphatic "No". By your definition, resize() should allocate new storage and initialize with the default constructor if I am reading this correctly. Then you would manipulate the objects by indexing into the collection and manipulating the reference instead of "inserting" into the collection. Otherwise, you need the copy constructor and assignment operator. All of the containers in the Standard Library have this requirement.

You might want to look into using something like boost::ptr_vector<T>. Since you are inserting pointers, you don't have to worry about copying. This would require that you dynamically allocate all of your objects though.

D.Shawley
your description of how resize would need to work is exactly what I want. the `boost` solution wouldn't work directly as all elements must be accessible immediately after the resize without having to manually allocate new objects.
BCS
I'm pretty sure that you will have to write a custom data structure for this. I don't recall ever seeing one that (1) doesn't require a copy constructor and (2) supports `resize()`. Qt's [`QList`](http://doc.trolltech.com/latest/qlist.html) comes close in that it will not copy elements internally but it still requires a copy constructor since it supports insert.
D.Shawley
@BCS: Actually the boost solution does work. `boost::ptr_vector<T>::resize` requires `T` to be default constructible because it inserts new `T` elements. This perfectly fits your requirements. There is also an overload of `resize` which takes a pointer and will clone the element you pass in.
Matthieu M.
@Matthieu: Interesting, I'm a bit surprised they wrote it that way as , aside from my case, I can't think of much use.
BCS
@BCS: this is because, by default, the Pointer Containers do not allow null elements. This means that they can offer a simpler interface where they return references instead of pointers (for example, dereferencing an iterator yields a reference, thus you don't have to always dereference).
Matthieu M.
@Matthieu: That's an interesting design choice. I'm not sure I would have considered it compelling enough to add yet another type that works that way.
BCS
@BCS: The main advantage of Pointer Containers isn't about simply adding dereferencing niceties (that's a polished interface is all), it's about having a container which both 1/ owns the variables pointed, and thus cleans up, 2/ offer polymorphic copies (via the Cloneability concept) and thus allow to store polymorphic objects via a pointer to base.
Matthieu M.
@Matthieu, Reading the docs it seems that null's are allowed, just not by default. Given that non-null is just a default, it's sounds a lot more reasonable.
BCS
@BCS: indeed, that's why the "by default" bit before "the Pointer Containers do not allow null elements". I really appreciate that they chose the safer alternative as default, it's so rare in C++ that it's worth noting!
Matthieu M.
+2  A: 

As you've discovered, all of the standard containers are incompatible with your requirements. If we can make a couple of additional assumptions, it wouldn't be too hard to write your own container.

  • The container will always grow - resize will always be called with a greater number than previously, never lesser.
  • It is OK for resize to make the container larger than what was asked for; constructing some number of unused objects at the end of the container is acceptable.

Here's a start. I leave many of the details to you.

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. If anybody asks you why you want to do this, tell them you need an array of std::auto_ptr. That should be good for a laugh.

Mark Ransom
They shoot people around here for even saying auto_ptr. http://www.ballerhouse.com/wp-content/uploads/cache/5a03e7c5a0bbe20b819696b42a6eea44.jpg
BCS
Your answer is the best one that really answers the OPs question.
Omnifarious
A: 

Look at ::boost::array. It doesn't allow the container to be resized after creating it, but it doesn't copy anything ever.

Getting both resize and no copying is going to be a trick. I wouldn't trust a ::std::deque because I think maybe it can copy in some cases. If you really need resizing, I would code your own deque-like container. Because the only way you're going to get resizing and no copying is to have a page system like ::std::deque uses.

Also, having a page system necessarily means that at isn't going to be quite as fast as it would be for ::std::vector and ::boost::array with their contiguous memory layout, even though it can still be fairly fast.

Omnifarious