How to Declare an Array of Objects Whose Class Has No Default Constructor

How do I declare an array of objects whose class has no default constructor?

For an array you would have to provide an initializer for each element of the array at the point where you define the array.

For a vector you can provide an instance to copy for each member of the vector.

e.g.

std::vector<Foo> thousand_foos(1000, Foo(42));

intializing a std::array of objects that don't have default constructor from another constexpr std::array

With std::index_sequence:

template <typename T, typename U, std::size_t N, std::size_t ... Is>
constexpr std::array<T, N> make_array(const std::array<U, N>& a, std::index_sequence<Is...>)
{
return {{T(a[Is])...}};
}

template <typename T, typename U, std::size_t N>
constexpr std::array<T, N> make_array(const std::array<U, N>& a)
{
return make_array<T>(a, std::make_index_sequence<N>());
}

Usage:

static constexpr std::array<int, 4> arr = {1,6,0,4};

/*constexpr*/ std::array<X, arr.size()> xArr = make_array<X>(arr);

Demo

Class member array of objects without default constructor and deleted copy constructors initialization

clang++ 3.6.2 compiles your code. g++ 5.2.0 does not.

According to this report, this is a bug in g++.


Here's a (nasty) workaround, for the moment: use std::aligned_storage, placement new and explicit destructor calls:

#include <type_traits>

class Container
{
private:
using Storage = typename std::aligned_storage
<
sizeof(DontCopyMe),
alignof(DontCopyMe)
>::type;

public:
Container()
{
// Construct an instance of `DontCopyMe` in the memory
// location starting at `&Array[0]`.
new (&Array[0]) DontCopyMe{1, "A"};

// ...
new (&Array[1]) DontCopyMe{2, "B"};
new (&Array[2]) DontCopyMe{3, "C"};

// You can also (and should) use a for-loop.
}

~Container()
{
// Interpret the bytes at location `&Array[2]` as if
// they were a `DontCopyMe` instance, then call the
// `~DontCopyMe()` destructor on it.
(reinterpret_cast<DontCopyMe*>(&Array[2]))->~DontCopyMe();

// ...
(reinterpret_cast<DontCopyMe*>(&Array[1]))->~DontCopyMe();
(reinterpret_cast<DontCopyMe*>(&Array[0]))->~DontCopyMe();

// You can also (and should) use a for-loop.
}

private:
Storage Array[3];
};

You'll have to implement move and copy operations for Containers that cleanup the aligned storage properly. You may want to wrap all aligned_storage-related code in an helper class.


You mentioned you require additional safety and constness. The best way to achieve this is wrapping std::aligned_storage in an helper class that will make sure you don't make mistakes.

Here's some code to get you started:

#include <type_traits>
#include <utility>
#include <cassert>

template<typename T, std::size_t TSize>
struct ASImmutableArray
{
private:
using ThisType = ASImmutableArray<T, TSize>;

using Storage = typename std::aligned_storage
<
sizeof(T),
alignof(T)
>::type;

Storage data[TSize];

template<typename... Ts>
void initAt(std::size_t mIndex, Ts&&... mXs)
{
assert(mIndex >= 0 && mIndex < TSize);
// assert the data was not initialized
new (&data[mIndex]) T(std::forward<Ts>(mXs)...);
}

void deinitAt(std::size_t mIndex)
{
assert(mIndex >= 0 && mIndex < TSize);
// assert the data was actually initialized
reinterpret_cast<T*>(&data[mIndex])->~T();
}

public:
// ...

};

An idea is passing std::tuple instances in the constructor of ASImmutableArray, and creating T instances in place using placement new at the correct indices by expanding the tuples and forwarding their contents to T's constructor. The tuples would contain the same types as the types required to construct T.

You can also keep track of initialized/deinitialized items with an additional member boolean array (that can be disabled in release builds, and only used for verifying the correct usage of the class during development).

If you want an example of an (old) implementation of something similar, this is something I've written for one of my libraries.

You can also check out this tagged union implementation I've written to see an example on how I use debug-only member variables that have no overhead in release-builds for additional safety.

Object array initialization without default constructor

Nope.

But lo! If you use std::vector<Car>, like you should be (never ever use new[]), then you can specify exactly how elements should be constructed*.

*Well sort of. You can specify the value of which to make copies of.


Like this:

#include <iostream>
#include <vector>

class Car
{
private:
Car(); // if you don't use it, you can just declare it to make it private
int _no;
public:
Car(int no) :
_no(no)
{
// use an initialization list to initialize members,
// not the constructor body to assign them
}

void printNo()
{
// use whitespace, itmakesthingseasiertoread
std::cout << _no << std::endl;
}
};

int main()
{
int userInput = 10;

// first method: userInput copies of Car(5)
std::vector<Car> mycars(userInput, Car(5));

// second method:
std::vector<Car> mycars; // empty
mycars.reserve(userInput); // optional: reserve the memory upfront

for (int i = 0; i < userInput; ++i)
mycars.push_back(Car(i)); // ith element is a copy of this

// return 0 is implicit on main's with no return statement,
// useful for snippets and short code samples
}

With the additional function:

void printCarNumbers(Car *cars, int length)
{
for(int i = 0; i < length; i++) // whitespace! :)
std::cout << cars[i].printNo();
}

int main()
{
// ...

printCarNumbers(&mycars[0], mycars.size());
}

Note printCarNumbers really should be designed differently, to accept two iterators denoting a range.

How to initialize array of classes with deleted copy constructor (C++11)

I agree with the comments that this seems to be a GCC bug (reported as 63707).

It only fails to compile when the type in the array has a user-defined destructor, which doesn't make sense to me.

How can I use unique pointers for an array of parameterized objects in C++ using Visual Studio 2017?

g++ 9.2.0 tells me that you lack default constructor, i.e. one without parameters. Adding such constructor works fine. If it's not what you want, you can create array of unique_ptr's, so std::unique_ptr<std::unique_ptr<Test>[]> and after that initialize each element by hand, something similar to this:

#include <memory>
#include <algorithm>
#include <iostream>

struct Test {
std::string str_;
Test(std::string const& str) : str_(str) { }
void print() { std::cout << str_ << '\n'; }
};

int main()
{
std::unique_ptr<std::unique_ptr<Test>[]> m_Tests;
int testCount = 2;
std::string path1{"a"}, path2{"b"};

m_Tests = std::make_unique<std::unique_ptr<Test>[]>(testCount);
std::array<std::string, 2> paths{path1, path2};
std::transform(paths.begin(), paths.end(), &m_Tests[0],
[](auto const& p) { return std::make_unique<Test>(p); });

for (int i = 0 ; i < testCount ; ++i) {
m_Tests[i]->print();
}
}



Related Topics



Leave a reply



Submit