Array initialization of objects without operator =, copy constructor or default constructor and run-time arguments

StackOverflow https://stackoverflow.com/questions/21599749

Question

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.

Was it helpful?

Solution 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_;
};

OTHER TIPS

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.

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;
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top