Object Array Initialization Without Default Constructor

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

How can I initialize an std::array of a class without a default constructor? [duplicate]

With copy constructor, something along these lines:

template <typename T, size_t... Is>
std::array<T, sizeof...(Is)> MakeArrayHelper(
const T& val, std::index_sequence<Is...>) {
return {(static_cast<void>(Is), val) ...};
}

template <typename T, size_t N>
std::array<T, N> MakeArray(const T& val) {
return MakeArrayHelper<T>(val, std::make_index_sequence<N>{});
}

std::array<Foo, 100> arr = MakeArray<Foo, 100>(Foo(5));

Actually, this can be done without copy constructor after all. This solution relies heavily on C++17's mandatory copy elision.

template <typename T, size_t... Is, typename... Args>
std::array<T, sizeof...(Is)> MakeArrayHelper(
std::index_sequence<Is...>, Args&&... args) {
return {(static_cast<void>(Is), T{std::forward<Args>(args)...}) ...};
}

template <typename T, size_t N, typename... Args>
std::array<T, N> MakeArray(Args&&... args) {
return MakeArrayHelper<T>(std::make_index_sequence<N>{},
std::forward<Args>(args)...);
}

Demo

Class with std::array of objects without default constructors

std::array is required to be an aggregate. Therefore it has no nontrivial constructors, but can be initialized using aggregate initialization. Note that aggregate initialization involves a braced-init-list (that is, a brace-enclosed list of initializers) but not an std::initializer_list object.

class Wrapper {
public:
Wrapper() : arr {MakeNoDefaultConstructor(123),
MakeNoDefaultConstructor(456)} {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ braced-init-list
private:
std::array<NoDefaultConstructor, 2> arr;
};

EDIT A constructor with variadic arguments could be possible here like so

#include <array>
struct NoDefault {
NoDefault() = delete;
NoDefault(int) {}
};
struct Wrapper {
template <typename... Args>
Wrapper(int b_in, Args&&... args) : b{b_in}, a{args...} {}
int b;
std::array<NoDefault, 3> a;
};
int main() {
std::array<NoDefault, 2> a {12, 34};
Wrapper w {23, 12, 34, 19};
}

This of course can be further tightly constrained by adding enable_ifs

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

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


Related Topics



Leave a reply



Submit