3

Disclaimer

I'm trying to allocate an array of objects that are neither copy constructible, assignable nor has a default constructor. The objects have arguments that are determined at run time. I know that you can solve this problem by having an array of pointers or cleverly using placement new but I'm more interested in if this is possible to do cleanly with C++11 (1y) magic. So please, this is purely of theoretical interest so avoid trying to solve "my problem" by offering a work around.

The code...

...So the question is: Is there a way to make the following work in C++11 or C++14:

class X{
public:
  explicit X(int a){...}
  X(const X&) = delete;
  void operator = (const X&) = delete;
private:
  ...
};

class Y{
public:
  Y(const std::vector<int>& args) {
    x = new X[]{args};
  }
  ~Y(){
    delete [] x;
  }
private:
  X* x;
};

Criteria

Specifically I'm looking for a solution/construct that meets the following criteria:

  • X is not copy constructible.
  • X is not assignable.
  • X does not have a default no-argument constructor (construction has intended side-effects).
  • The arguments to X's constructor are not known until run time.
  • All instances of X must be laid out contiguously in memory.
  • X must be properly destructed when the array is deleted from it's base pointer (or if an intermediate class is used, when the intermediate object is destructed). This rules out array of pointers and naivë use of placement new.

Edit / Addendum

I forgot to mention that move constructor is not available. In the actual case at hand, X spawns a worker thread and is executing in the context of this of the initially constructed object, any attempt to use a move constructor will corrupt the state of the executing thread.

11
  • 3
    Use vector and emplace_back. Commented Feb 6, 2014 at 10:11
  • @Fernandes Re: edit. I used the term "C++1x" specifically to mean answers in C++11 or C++14. Please see: meta.stackexchange.com/questions/11474/… It was neither a grammatical or spelling error, nor was it a clarification (you changed my meaning). Commented Feb 6, 2014 at 10:20
  • 2
    Please use "C++11 or C++14" then. "C++1x" was once used to mean "the soon to be released version of C++ that used to be referred to as C++0x but will now be released in the second decade of the third millennium". It does not help clarity. Commented Feb 6, 2014 at 10:22
  • 6
    (a) The edit fixed a noticeable problem with your question. The entire C++ community, including the standard working group, uses C++1y to mean C++14. It never means C++11. And C++1x simply doesn't exist, but where it does, it is ambiguous due to C++0x's history. So your intent was misrepresented until Martinho very kindly fixed it for you. A "thank you" would be appropriate. (b) It's not your post; this is a Q&A database, not your personal programming helpdesk. Commented Feb 6, 2014 at 10:33
  • 3
    (c) No consent is required: you explicitly gave up all claims to that by posting under the CC By-SA 3.0 licence, and reading the FAQ that explains all about how this site works. Hope that alleviates some confusion for you. Also, (d) don't be so rude. Commented Feb 6, 2014 at 10:34

3 Answers 3

4

You can use std::vector and its emplace_back function if you make X at least movable.

class X{
public:
  explicit X(int){}
  X(X&&) = default;
  X(const X&) = delete;
  void operator = (const X&) = delete;
};

int main() {
    std::vector<X> xs;
    xs.emplace_back(0);
    xs.emplace_back(1);
    xs.emplace_back(2);
    xs.emplace_back(3);
}

(If you declare a copy constructor, even if that declaration deletes it, the compiler will not automatically generate any special move member, so you need to explicitly request them)

This basically boils down to the "array with placement new" strategy, but all abstracted away into high-level notions.

If you cannot make use of a movable type, you have to implement a vector-like class that pre-allocates storage and never reallocates. There is nothing similar in the standard library.

Sign up to request clarification or add additional context in comments.

4 Comments

I knew I forgot something, I can't use the move constructor due to side effects in the constructor (starting a worker thread executing code in the context of this). Added a clarification to the post.
FWIW, the move constructor is only required for compilation. If you call reserve beforehand it will not be used. If you really want to avoid it, you'll have to write a vector-like class that never reallocates.
Then I run the risk of inadvertently using that move constructor somewhere in a daft moment. ;\ Sure I can just throw and catch it at compile time but I don't like that.
Must this be used as context, can not a member pointer to a struct be used as the context, which then can actually be moved in the move constructor?
4

You're going to have to keep track of the constructed elements by hand, but you can use allocator to help:

class Y{
public:
  Y(const std::vector<int>& args):
    alloc{},
    n{args.size()},
    x{alloc.allocate(n)}
  {
    auto end = x;
    try {
      for (auto arg: args)
        alloc.construct(end++, arg);
    } catch (...) {
      while (end != x)
        alloc.destroy(--end);
      alloc.deallocate(x, n);
      throw;
    }
  }
  ~Y() {
    for (auto end = std::next(x, n); end != x; --end)
      alloc.destroy(end);
    alloc.deallocate(x, n);
  }
private:
  std::allocator<X> alloc;
  const std::size_t n;
  const X *x;
};

5 Comments

I would inherit privately from std:: allocator to take advantage of the empty base optimization. And probably parameterize on X and the allocator type because everything deserves to be a template ;)
Also, it's not necessary to store both the size and end pointer, since the entire range is always initialized in the constructor. Either one will suffice.
@Casey if one of the Xs throws on construction, we need to destroy only the previous elements. If X was nothrow constructible from int then, yes, we could eliminate the end pointer.
If one of the Xs throws the construction won't complete, so the destructor won't run. You need a try/catch/throw and manual cleanup or encapsulation in another class that just handles deallocation/destruction.
@Casey of course! Fixed. This is why writing exception-safe containers is hard.
3

A class that is neither copyable nor movable, nor has a default constructor, cannot be held in a standard container (doesn't meet the requirements) or a variable-sized array allocation (which only allows argument specification for a fixed number of elements).

This means you need to allocate raw memory instead and use placement new to construct the objects. You can wrap this in a fixed-space vector class.

template <typename T>
class fixed_capacity_vector {
public:
  using size_type = std::size_t;

  fixed_capacity_vector(size_type capacity)
    : data_(::operator new(capacity * sizeof(T)), size_(), capacity_(capacity)
  {}

  fixed_capacity_vector(const fixed_capacity_vector&) = delete;
  fixed_capacity_vector(fixed_capacity_vector&&) = delete;
  fixed_capacity_vector& operator =(const fixed_capacity_vector&) = delete;
  fixed_capacity_vector& operator =(fixed_capacity_vector&&) = delete;

  ~fixed_capacity_vector() {
    for (size_type i = 0; i < size_; ++i) data_[i].~T();
    ::operator delete(data_);
  }

  template <typename... Args>
  T& emplace_back(Args&&... args) {
    if (size_ == capacity_) throw out_of_range();
    new (data_ + size_) T(std::forward<Args>(args)...);
    ++size_;
    return data_[size_-1];
  }

private:
  T* data_;
  size_type size_;
  size_type capacity_;
};

4 Comments

I'd recommend doing destruction from last to first, as destruction is usually performed in the the reverse order of construction everywhere in the language and standard library (std::vector does this too). Be careful with unsigned types in decreasing loops, though.
Yeah, the unsigned thing is why I did it forward. In a real implementation, I'd have iterators and reverse iterators and I would use those.
There is no contiguous standard container that can store a non-copyable, non-moveable non-default constructible type. Relax the contiguous requirement and you can use a deque or a list.
Whats wrong with while(size_) data[--size_].~T(); ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.