Move semantics for a custom vector and allocators - c++

I'm implementing a basic std::vector by using an allocator, following PPP by Stroustrup, and in particular I'm sure that all the functions like resize, push_back, emplace_back, and so on are okay.
What is really puzzling me a bit is the copy and move semantics: it works now, but I find it a bit too hard-coded and, more importantly, it's leaking 1 byte in the move assignment, as can be seen from this
==7853== LEAK SUMMARY:
==7853== definitely lost: 1 bytes in 1 blocks
==7853== indirectly lost: 0 bytes in 0 blocks
==7853== possibly lost: 0 bytes in 0 blocks
In the following the whole code. I'd like to see especially how the move assignment should be implemented, because I believe that a problem is the fact that I have to move an allocator, and I'm totally new to this topic.
#include <iostream>
#include <memory>
#include <utility>
#include <initializer_list>
#include <string>
template <typename T, typename Allocator = std::allocator<T>>
class Vector
{
private:
Allocator allocator; //used to handle memory for the elements
T *elem;
std::size_t _size{};
std::size_t _capacity{};
void reserve(const std::size_t n)
{
if (_capacity < n) //if the capacity is smaller than what I want to allocate
{
T *tmp{std::allocator_traits<Allocator>::allocate(allocator, n)};
for (std::size_t i = 0; i < _size; ++i)
{
// allocator.construct(tmp + i, std::move(elem[i])); //tmp[i] = std::move(elem[i]);
std::allocator_traits<Allocator>::construct(allocator, tmp + i, elem[i]);
}
for (std::size_t i = 0; i < _size; ++i)
{
std::allocator_traits<Allocator>::destroy(allocator, elem + i);
}
std::allocator_traits<Allocator>::deallocate(allocator, elem, _capacity);
elem = tmp; //move the pointer
_capacity = n; //size increased!
}
}
void check_and_increase_capacity()
{
if (_capacity == 0)
{
reserve(8);
}
else if (_size == _capacity)
{
reserve(2 * _size);
}
}
template <typename O>
void _push_back(O &&x)
{
check_and_increase_capacity();
std::allocator_traits<Allocator>::construct(allocator, elem + _size, std::forward<O>(x));
++_size;
}
public:
explicit Vector(std::initializer_list<T> list) : elem{std::allocator_traits<Allocator>::allocate(allocator, list.size())}, _size{list.size()}, _capacity{list.size()}
{
std::uninitialized_copy(list.begin(), list.end(), begin());
std::cout << "custom cstr"
<< "\n";
}
~Vector() noexcept
{
for (std::size_t i = 0; i < _size; ++i)
{
std::allocator_traits<Allocator>::destroy(allocator, elem + i); //call the destructor
}
std::allocator_traits<Allocator>::deallocate(allocator, elem, _capacity); //deallocate memory
}
T *begin() { return elem; }
const T *begin() const { return elem; }
T *end() { return elem + _capacity; }
const T *end() const { return elem + _capacity; }
Vector(const Vector &v) : allocator{std::allocator_traits<Allocator>::select_on_container_copy_construction(v.allocator)}, elem{std::allocator_traits<Allocator>::allocate(allocator, v._capacity)}
{
T *tmp{std::allocator_traits<Allocator>::allocate(allocator, _capacity)};
_size = v._size;
_capacity = v._capacity;
for (std::size_t i = 0; i < _size; ++i)
{
std::allocator_traits<Allocator>::construct(allocator, tmp + i, std::move(v[i]));
}
std::uninitialized_copy(v.begin(), v.end(), begin()); //copy the elements
//destroy and deallocate tmp
for (std::size_t i = 0; i < _size; ++i)
{
std::allocator_traits<Allocator>::destroy(allocator, tmp + i);
}
std::allocator_traits<Allocator>::deallocate(allocator, tmp, _capacity);
}
Vector &operator=(const Vector &v)
{
T *tmp{std::allocator_traits<Allocator>::allocate(allocator, v._capacity)};
std::uninitialized_copy(v.begin(), v.end(), begin()); //copy the elements
for (std::size_t i = 0; i < v._size; ++i)
{
std::allocator_traits<Allocator>::destroy(allocator, elem + i);
}
std::allocator_traits<Allocator>::deallocate(allocator, elem, _capacity);
elem = tmp;
_size = v._size;
_capacity = v._capacity;
return *this;
}
Vector(Vector &&v) noexcept : allocator{std::move(v.allocator)}
{
elem = v.elem;
v.elem = nullptr;
_size = v._size;
v._size = 0;
_capacity = v._capacity;
v._capacity = 0;
std::cout << elem << "\n";
}
Vector &operator=(Vector &&v) noexcept
{
std::cout << "move assignment"
<< "\n";
allocator = std::move(v.allocator);
elem = v.elem;
v.elem = nullptr;
_size = v._size;
v._size = 0;
_capacity = v._capacity;
v._capacity = 0;
return *this;
}
void push_back(const T &x)
{
_push_back(x);
}
void push_back(T &&x)
{
_push_back(std::move(x));
}
template <typename... Types>
void emplace_back(Types &&...args)
{
check_and_increase_capacity();
std::allocator_traits<Allocator>::construct(allocator, elem + _size, std::forward<Types>(args)...);
}
T &operator[](const std::size_t i) noexcept { return elem[i]; }
const T &operator[](const std::size_t i) const noexcept { return elem[i]; }
friend std::ostream &operator<<(std::ostream &os, const Vector &v)
{
for (std::size_t i = 0; i < v._size; i++)
{
std::cout << v[i] << "\n";
}
return os;
}
void resize(const std::size_t newsize, T val = T{})
{
reserve(newsize);
for (std::size_t i = _size; i < newsize; ++i)
{
std::allocator_traits<Allocator>::construct(allocator, elem + i, val);
}
//destroy all the new extra elements
for (std::size_t i = newsize; i < _size; ++i)
{
std::allocator_traits<Allocator>::destroy(allocator, elem + i); //just destroy them, don't call release the memory!
}
_size = newsize;
}
};
struct Foo
{
std::string _s;
Foo()
{
std::cout << "foo cstr"
<< "\n";
};
explicit Foo(const std::string &s) : _s{s} {}
~Foo() = default;
};
int main()
{
Vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::cout << v << "\n";
v.push_back(11);
std::cout << "After push_back \n"
<< v << "\n";
Vector<Foo> w{{}, {}};
w.emplace_back();
v.resize(6);
std::cout << "After resize \n"
<< v << "\n";
// Copy/Move semantics tests
Vector<int> v1{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Vector<int> v2{v1};
std::cout << "After copy cstr \n"
<< v2 << "\n";
v2.push_back(20);
std::cout << "after push_back \n"
<< v2 << "\n";
Vector<int> v3{};
v3 = v1;
std::cout << v3 << "and v1: \n"
<< v1 << "\n";
Vector<int> v4{std::move(v1)};
std::cout << v4 << "and v1: \n"
<< v1 << "\n";
Vector<int> v5{};
v5 = std::move(v4);
std::cout << v5 << "and v4: \n"
<< v4 << "\n";
return 0;
}

Related

C++: Disassemble a flat vector into multiple vectors of equal size without copying

Is it possible in C++ to split a flat vector (or C style array) into multiple vectors of equal size without copying any of its containing data? That is, disassembling the original vector by moving its content to a new vector, which invalidates the original vector. The following code example should illustrate this:
#include <cassert>
#include <vector>
void f(int* v) {
for (int i = 0; i < 100; i++) {
v[i] = i;
}
}
/**
* Split v into n vectors of equal size without copy its data (assert v.size() % n == 0)
*/
std::vector<std::vector<int>> g(std::vector<int> v, int n) {
std::vector<std::vector<int>> vs(n);
int vec_size = v.size() / n;
for (int i = 0; i < n; i++) {
vs[i].assign(v.begin() + i * vec_size, v.begin() + (i + 1) * vec_size); // copies?
// how to let vs[i] point to v.begin() + i * vec_size?
}
return vs;
}
int main() {
std::vector<int> v(100);
f(v.data());
std::vector<std::vector<int>> vs = g(std::move(v), 10);
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
assert(vs[i][j] == i * 10 + j);
}
}
return 0;
}
Yes, in my opinion this is possible. Moving the elements, but not copying the elements.
C++ offers std::make_move_iterator. Please read here about that.
To check that, I created a small class to output, to see, if we copy or move something.
So, if your data can "move", then it will work, otherwise of course a copy will be made. With the following we see the result.
struct Test {
int data{};
Test(int d) : data(d) { std::cout << "Construct and init\n"; }
Test() { std::cout << "Default construct\n"; };
~Test() { std::cout << "Destruct\n"; };
Test(const Test& other) { std::cout << "Construct\n"; data = other.data; }
Test(const Test&& other) noexcept { std::cout << "Move Construct\n"; data = other.data; }
Test& operator =(const Test& other) noexcept { std::cout << "Assign\n"; data = other.data; return *this; }
Test& operator =(const Test&& other) noexcept { std::cout << "Move Assign\n"; data = other.data; return *this; }
};
We will additionally add a small function, which calculates the offsets of the chunks that will be moved.
And then, we can come up with a small function to implement that.
#include <iostream>
#include <vector>
#include <numeric>
#include <iterator>
#include <iomanip>
// Calculate start and end index for all chunks
std::vector<std::pair<size_t, size_t>> calculatePairs(const size_t low, const size_t high, const size_t numberOfGroups) {
// Here we will store the resulting pairs with start and end values
std::vector<std::pair<size_t, size_t>> pairs{};
// Calculate chung size and remainder
const size_t delta = high - low;
const size_t chunk = delta / numberOfGroups;
size_t remainder = delta % numberOfGroups;
// Calculate the chunks start and end addresses for all chunks
size_t startIndex{}, endIndex{};
for (size_t i = 0; i < numberOfGroups; ++i) {
// Calculate end address and distribute remainder equally
endIndex = startIndex + chunk + (remainder ? 1 : 0);
// Store a new pair of start and end indices
pairs.emplace_back(startIndex, endIndex);
// Next start index
startIndex = endIndex;
// We now consumed 1 remainder
if (remainder) --remainder;
}
//--pairs.back().second;
return pairs;
}
struct Test {
int data{};
Test(int d) : data(d) { std::cout << "Construct and init\n"; }
Test() { std::cout << "Default construct\n"; };
~Test() { std::cout << "Destruct\n"; };
Test(const Test& other) { std::cout << "Construct\n"; data = other.data; }
Test(const Test&& other) noexcept { std::cout << "Move Construct\n"; data = other.data; }
Test& operator =(const Test& other) noexcept { std::cout << "Assign\n"; data = other.data; return *this; }
Test& operator =(const Test&& other) noexcept { std::cout << "Move Assign\n"; data = other.data; return *this; }
};
std::vector<std::vector<Test>> split(std::vector<Test>& v, unsigned int n) {
std::vector<std::vector<Test>> result{};
if (v.size() > n) {
result.resize(n);
std::vector<std::pair<size_t, size_t>> offset = calculatePairs(0u, v.size(), n);
for (size_t i{}; i < n; ++i) {
result[i].insert(result[i].end(), std::make_move_iterator(v.begin() + offset[i].first),
std::make_move_iterator(v.begin() + offset[i].second));
}
}
return result;
}
constexpr size_t NumberOfElements = 30u;
constexpr unsigned int NumberOfGroups = 3;
static_assert (NumberOfElements >= NumberOfGroups, "Number of elements must be greater/equal then number of elements\n");
int main() {
std::cout << "\n\n\nCreate vector with " << NumberOfElements << " elements\n\n";
std::vector<Test> v1(NumberOfElements);
std::cout << "\n\n\nFill vector with std::iota\n\n";
std::iota(v1.begin(), v1.end(), 1);
std::cout << "\n\n\nSplit in " << NumberOfGroups<< "\n\n";
std::vector<std::vector<Test>> s = split(v1, NumberOfGroups);
std::cout << "\n\n\nOutput\n\n";
for (const std::vector<Test>& vt : s) {
for (const Test& d : vt) std::cout << std::setw(3) << d.data << ' ';
std::cout << "\n\n";
}
}
But my strong guess is that you want to splice the data. The underlying elements fo the std::vector which you can get with the data() function.
You can access the data easily with pointer arithmetic on data().
But if you want to have the data in a new container, then this is difficult with a std::vector. It can for example be done with a std::list that has a splice function and does, what you want.
Or, you need to implement your own dynamic array and implement a splice function . . .
Checksum:
;23M#eTo1?:B#r7C8#wtJ'Z'..uIvLT.j;bld$Bvgjd.qm=8;B/`dHM%D#wyv:\5YI:WVGwJL00%IsKQ9O+&#g,/gzkPg^cg::LX?6dL3;Fs3GOOAmQmCIW?&skWxZXsElyn6S3#fi:0DSKJ/A^r#*'k#a#e8!XDpjAUtu?K5iu+e=P"M7a2BWdFdA.5NP:Y"l,,h+Y/PxhVfP/m0ceS=Nxol2vOZwM2+!H\^a=douX%fhqcr4'0eXiEZeKvTf0^%CTNY^WB6fc#IpK^GQgxTXQo0ikr0+/OxXlc1B$5jc1r,GQj+fwEdoCPrz6,j:SO6L3QU#7lT:f#Y^V!Au\P'a5amR$NCU?\WspBOuy#RH3tJimka#rdyNN56.$;DtRCHN*YeWlrG=',XNSrzEK:Cw;#A%.#/:c,a2W24IIIdecc7O"EnKQn;nXmUemX4kclDsYci+izmr#vlGAQ.w2!cuf;6n2UvJM,CeSyRj1,:2\9#i8GLwtux!uEHUp7X*5SC%nld956CHsy&/n73/90cRP'Me"1PW+##FH8mH4Rf^o=ZP/Rm\X&1syUdUh+.N/jtoO:,OBBAmq,jW69Fu%jJukBa$g4hIrIPcxx17i;XU,FCbQGd8v*AyKGSML\JN#jte*F";Zh7fqhvCXobE&SapX90r"Z$.CN,1R^aj.=5L6^tUB2UPJH^eb'*B!v5=D.9PFI#Pt*KjK+yS*tV6f.5kgPOzBE$uK0MA/\l9U"63LUR6k3#'cub?u&xILMXP%#:lx2TbKhFOjBpMN!+%F16jrgv&AoFhuf%P!==8?x,NsSd%hVo"BJhVv3rjrhvM"WLE3%y#N7g37Re^XiS9lpyKA9E7ow6U=I"tlv",&#+fZoIR4KM58!NTm978wCI?9wo.ocS!9i5k#ler47J.G0yXjZVSdr=G"uRodC06k\V%8;oUwV&z!W5:+ZvE:nyO#+lO+Hn0&tnH&^tNC?'PmERxs/B+KW4O6&oWDED9?MqxmYgVKoT.a%iw

What's the suited container to push values on top, remove at whatever index and avoid memory reallocation?

I need to build a sort of stack where I can push values on top:
5 // (size 1)
5 3 // (size 2)
5 3 8 // (size 3)
than remove them by value, such as removing 3:
5 8 // (size 2)
than be able to always get the last value (i.e. 8 in the example), when I need it).
I can push max 32 values, so I know the whole size (avoiding heap?).
I think to std::vector with:
initial reserve(32)
.push_back() for insert
vector.erase(std::remove(vector.begin(), vector.end(), value), vector.end()) for remove by value
vector[vector.size() - 1] to retrieve the last element
But maybe there are some stl container better for this kind of process? Not sure if vector are always in the stack and will do further memory reallocation under the hood...
You can write an allocator that contains your 32 values, and refuses to allocate any amount other than 32
template <typename T, std::size_t N = 32>
struct static_allocator
{
T* allocate(std::size_t n) { if (n != N) throw std::bad_alloc(); return arr; }
void deallocate(T *, std::size_t) {}
using pointer = T*;
using const_pointer = const T*;
using void_pointer = void*;
using const_void_pointer = const void*;
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
template <typename U>
struct rebind
{
using other = static_allocator<U, N>;
};
static_allocator select_on_container_copy_construction() { return {}; }
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
private:
T arr[N];
};
Then a std::vector<T, static_allocator<T>> will have it's elements as subobjects.
I don't think it's possible to avoid dynamic allocation and have sublinear random-access remove.
if size is limited to 32 elements
why not use a circular buffer of 32 elements, and roll the elements when they are 32 ?
There may be some bugs (don't use last() or remove () on an empty container, don't remove an element not inserted...) ,but it works for the functions you wanted. Here is the idea (heap is avoided)
#include <iostream>
template <typename T>
class Container {
public :
static const int smax_ = 32;
void erase () {
T* pt ((T*) val_);
for (int i (0); i != smax_; ++i, ++pt) *pt = 0;
size_ = 0;
}
Container () : size_ (0) { erase ();}
~Container () {}
void copy (const Container& c) {
size_ = c.size_;
T* pt ((T*) val_);
const T* qt ((const T*) c.val_);
for (int i (0); i != size_; ++i, ++pt, ++qt) *pt++ = *qt++;
}
Container (const Container& c) {
copy (c);
}
void push_back (const T& t) {
if (size_ == smax_) {
T* pt ((T*) val_);
const T* qt ((const T*) val_);
++qt;
for (int i (0); i != size_ -1; ++i, ++pt, ++qt) {
*pt = *qt;
}
*pt = t;
}
else {
val_ [size_] = t;
++size_;
}
}
int size () const {
return size_;
}
void remove (const T& t) {
if (!size_) return;
int i (0);
T* pt ((T*)val_);
while ((i < smax_) && (*pt != t)) {
++pt; ++i;
}
if (i != smax_) {
T* qt (pt);
++qt;
for (; i != size_ -1; ++i, ++pt, ++qt) {
*pt = *qt;
}
}
--size_;
}
void write (std::ostream& os) const {
const T* pt ((const T*) val_);
for (int i (0); i != size_; ++i, ++pt) os << *pt << " ";
}
bool operator == (const Container& c) const {
if (size_ != c.size_) return false;
const T* pt ((const T*) val_), *qt ((const T*) c.val_);
for (int i (0); i != size_; ++i, ++pt, ++qt) if (*pt != *qt) return false;
return true;
}
bool operator != (const Container& c) const {
return !operator == (c);
}
T& operator = (const Container& c) {
copy (c);
return *this;
}
T last () const {
return val_ [size_ -1];
}
T val_ [smax_];
int size_;
};
Test Program
int main (int argc, char* argv []) {
Container<int> c;
std::cout << "pushing back 5..." << std::endl;
c.push_back (5);
c.write (std::cout);
std::cout << std::endl;
std::cout << "c.last == " << c.last () << std::endl;
std::cout << "pushing back 3..." << std::endl;
c.push_back (3);
c.write (std::cout);
std::cout << std::endl;
std::cout << "c.last == " << c.last () << std::endl;
std::cout << "pushing back 8..." << std::endl;
c.push_back (8);
c.write (std::cout);
std::cout << std::endl;
std::cout << "c.last == " << c.last () << std::endl;
std::cout << "erasing 3..." << std::endl;
c.remove (3);
c.write (std::cout);
std::cout << std::endl;
std::cout << "c.last == " << c.last () << std::endl;
}
and the results :
pushing back 5...
5
c.last == 5
pushing back 3...
5 3
c.last == 3
pushing back 8...
5 3 8
c.last == 8
erasing 3...
5 8
c.last == 8
if you dont want memory reallocation then you can also use list container i.e linked list ..as it has mostly same properties to the vector..just it do not support random access or []operator ...else vector is perfect:)

Why my code doesn't work with custom allocator?

What is the problem and how to fix it?
Without trying to squeeze a custom allocator, my vector, at first glance, works correctly.
I will also be happy to point out any mistakes in my code. Or an example of the correct implementation of a custom vector or another container, that would help me.
This code doesn't work:
using MyLib::Vector;
int main()
{
//Vector<int> v; //Works fine
Vector<int> v(CustomAllocator<int>());
for (size_t i = 0; i < 256; i++) {
v.push_back(i); //Error: "expression must have class type"
}
}
CustomAllocator implementation (it should be fine):
template <typename T>
class CustomAllocator : public std::allocator<T>
{
private:
using Base = std::allocator<T>;
public:
T* allocate(size_t count){
std::cout << ">> Allocating " << count << " elements" << std::endl;
return Base::allocate(count);
}
T* allocate(size_t count, const void* p)
{
std::cout << ">> Allocating " << count << " elements" << std::endl;
return Base::allocate(count, p);
}
void deallocate(T* p, size_t count)
{
if (p != nullptr)
{
std::cout << ">> Deallocating " << count << " elements" << std::endl;
Base::deallocate(p, count);
}
}
};
Vector implementation:
namespace MyLib
{
template <typename T,
template <typename Y> class Allocator = std::allocator>
class Vector
{
private:
std::size_t capacityV;
std::size_t sizeV;
Allocator<T> alloc;
T* arr;
public:
typedef Allocator<T> AllocatorType;
typedef Vector<T, Allocator> VectorType;
using AllocTraits = std::allocator_traits<Allocator<T>>;
public:
explicit Vector(const AllocatorType& allocator = AllocatorType()) {
capacityV = 0;
sizeV = 0;
alloc = allocator;
arr = nullptr;
}
Vector(const std::initializer_list<T>& values,
const AllocatorType& allocator = AllocatorType()) {
sizeV = values.size();
alloc = allocator;
if (sizeV < 128)
capacityV = 128;
else
capacityV = (sizeV / 128) * 256; //that makes sense
arr = AllocTraits::allocate(alloc, capacityV);
AllocTraits::construct(alloc, arr, capacityV);
std::copy(values.begin(), values.end(), arr);
}
~Vector() {
if (arr)
AllocTraits::deallocate(alloc, arr, capacityV);
}
Vector(const Vector& rhs) {
capacityV = rhs.capacityV;
sizeV = rhs.sizeV;
arr = AllocTraits::allocate(alloc, capacityV);
std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
}
Vector(Vector&& rhs) noexcept {
capacityV = std::move(rhs.capacityV);
sizeV = std::move(rhs.sizeV);
arr = std::move(rhs.arr);
}
Vector& operator = (const Vector& rhs) {
capacityV = rhs.capacityV;
sizeV = rhs.sizeV;
arr = AllocTraits::allocate(alloc, capacityV);
std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
}
Vector& operator = (Vector&& rhs) {
capacityV = std::move(rhs.capacityV);
sizeV = std::move(rhs.sizeV);
arr = std::move(rhs.arr);
}
T& operator [](std::size_t i) noexcept {
if (i < sizeV)
return arr[i];
else
throw std::out_of_range("Wrong index!");
}
const T& operator [](std::size_t i) const noexcept {
if (i < sizeV)
return arr[i];
else
throw std::out_of_range("Wrong index!");
}
T* data() noexcept {
return arr;
}
const T* data() const noexcept {
return arr;
}
void push_back(const T& value) {
++sizeV;
if (!arr) {
if (!capacityV)
capacityV = 128;
arr = AllocTraits::allocate(alloc, capacityV);
}
if (sizeV > capacityV) {
if(capacityV > UINT32_MAX - 256)
throw std::runtime_error("Vector overflowed!");
size_t tmpCap = capacityV;
capacityV = (sizeV / 128) * 256; //Увеличим capacityV
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
arr[sizeV - 1] = value;
}
void push_back(T&& value) {
++sizeV;
if (!arr) {
if (!capacityV)
capacityV = 128;
arr = AllocTraits::allocate(alloc, capacityV);
}
if (sizeV > capacityV) {
if (capacityV > UINT32_MAX - 256)
throw std::runtime_error("Vector overflowed!");
size_t tmpCap = capacityV;
capacityV = (sizeV / 128) * 256; //Увеличим capacityV
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
arr[sizeV - 1] = std::move(value);
}
void pop_back() {
--sizeV;
}
void resize(std::size_t size) {
if (this->sizeV == size)
return;
if (this->sizeV > size) {
this->sizeV = size;
}
else {
size_t tmpSize = size;
if (capacityV >= size) {
this->sizeV = size;
for (size_t i = tmpSize - 1; i < this->sizeV; i++)
arr[i] = 0;
}
else {
size_t tmpCap = capacityV;
capacityV = (size / 128) * 256; //that makes sense
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
this->sizeV = size;
for (size_t i = tmpSize - 1; i < this->sizeV; i++)
arr[i] = 0;
}
}
}
void reserve(std::size_t capacity) {
if (capacity > this->capacityV)
{
size_t tmpCap = capacity;
this->capacityV = capacity;
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
}
std::size_t size() const noexcept {
return sizeV;
}
std::size_t capacity() const noexcept {
return capacityV;
}
bool empty() const noexcept {
return (sizeV == 0);
}
};
}
Vector<int> v(CustomAllocator<int>());
You got hit by the most vexing parse. Vector<int> v(CustomAllocator<int>()); can be parsed as a variable declaration or a function declaration and the grammar prefers the latter. Therefore, the compiler thinks that v is a function and this it why you get the "expression must have class type" error -- you can only invoke methods on values with a class type, but v is a function.
Even if you fixed that error using one of these options:
// C++03 solution (extra parens)
Vector<int> v((CustomAllocator<int>()));
// C++11 solution (uniform initialization)
Vector<int> v{CustomAllocator<int>{}};
Your code still wouldn't do what you expected, though it would run. Vector<int> is the same thing as Vector<int, std::allocator> and so v will still use the standard allocator.
Why doesn't this cause a compilation error? Because CustomAllocator<int> inherits std::allocator<int> (which it shouldn't!), so the std::allocator<int> copy constructor is used to slice your custom allocator into an std::allocator<int> and then the program proceeds using the standard allocator. Your CustomAllocator<int> temporary is basically converted into an std::allocator<int>.
To illustrate, both of the above "fixed" examples are roughly equivalent to this code (if we disregard some value copies/moves that are irrelevant to the program's observable behavior):
// Creates a custom allocator value.
CustomAllocator<int> a;
// Custom allocator is converted to std::allocator<int>.
std::allocator<int> b(a);
// std::allocator<int> is provided to the Vector constructor.
Vector<int> v(b);
The correct fix is to specify the second Vector type parameter and then the constructor arguments aren't even needed since the default constructor will do the right thing:
Vector<int, CustomAllocator> v;

Array template class in C++

I can not get the idea of how to create Array template class properly in C++.
The problem is solely out of learning purposes.
Let me provide the code first.
Array.h :
//Developed by Trofimov Yaroslav on 30.03.2018
#ifndef _ARRAY_H_TROFIMOV_
#define _ARRAY_H_TROFIMOV_
#include <string>
template<const size_t n, typename T>
class Array {
static unsigned __freeId, __quantity;
unsigned _id;
T** _array;
const size_t _n;
public:
typedef const bool (* const BooleanResultDelegate)(const T&);
class ArrayError {
const std::string _reason;
const size_t _index;
const size_t _maxIndex;
public:
ArrayError(const size_t index, const size_t maxIndex,const std::string& reason = "")
: _index(index), _maxIndex(maxIndex), _reason(reason) {}
std::string explanation(void) {
std::string res += "Index: " + std::to_string(_index) + "\n";
res += "Max index: " + std::to_string(_maxIndex) + "\n";
res += "Reason: " + _reason + "\n";
return res;
}
};
explicit Array<n, T>(T* arrayFiller = 0)
: _n(n), _array(new T*[n]), _id(++__freeId) {
if(arrayFiller != 0) {
for(size_t i(0); i < length(); ++i) {
_array[i] = new T(*arrayFiller);
}
} else {
for(size_t i(0); i < length(); ++i) {
_array[i] = arrayFiller;
}
}
reportIfDebug<n, T>(*this, "created");
++__quantity;
}
explicit Array<n, T>(const T& arrayFiller)
: _n(n), _array(new T*[n]), _id(++__freeId) {
for(size_t i(0); i < length(); ++i) {
_array[i] = new T(arrayFiller);
}
reportIfDebug<n, T>(*this, "created");
++__quantity;
}
Array<n, T>(const Array<n, T>& that)
: _n(n), _array(new T[n]), _id(++__freeId) {
for(size_t i(0); i < length(); ++i) {
(*this)[i] = new T[that[i]];
}
reportIfDebug<n, T>(*this, "created");
++__quantity;
}
~Array<n, T>(void) {
removeAll();
delete [] _array; _array = 0;
reportIfDebug<n, T>(*this, "deleted", false);
--__quantity;
}
T* operator[](const size_t i) {
if(i > length()) {
throw ArrayError(i, _n, "out of bounds exception");
}
return _array[i];
}
const T* operator[](const size_t i) const {
if(i > length()) {
throw ArrayError(i, _n, "out of bounds exception");
}
return _array[i];
}
const size_t length() const {
return _n;
}
const unsigned getID() const {
return _id;
}
void removeAll(BooleanResultDelegate removeCondition = 0) {
for(size_t i(0); i < length(); ++i) {
if(removeCondition == 0 || removeCondition(*_array[i])) {
delete [] _array[i]; _array[i] = 0;
}
}
}
};
template<const size_t n, typename T>
unsigned Array<n, T>::__freeId(0);
template<const size_t n, typename T>
unsigned Array<n, T>::__quantity(0);
template<const size_t n, typename T>
void reportIfDebug(
const Array<n, T>& instance,
const char* const message,
const bool showContent = true) {
#ifndef NDEBUG
std::cout << "========================================" << std::endl;
std::cout << typeid(instance).name() << ' '
<< message << ' '
<< "id: " << instance.getID() << std::endl;
if(showContent) {
std::cout << instance;
}
std::cout << "========================================" << std::endl;
#endif
}
template<const size_t n, typename T>
std::ostream& operator<<(std::ostream& os, const Array<n, T>& instance) {
for(size_t i(0); i < instance.length(); ++i) {
if(instance[i] == 0) {
os << "[" << i << "]: " << instance[i] << "\n";
} else {
os << "[" << i << "]: " << *instance[i] << "\n";
}
}
return os;
}
#endif
Main.cpp :
//Developed by Trofimov Yaroslav on 30.03.2018
#include <iostream>
#include "Array.h"
int main(void) {
const Array<5, int> a(7);
std::cout << *a[2] << std::endl;
return 0;
}
What is the main problem right now - is that the client of my Array class would have to use [indirection operator *] and [0 value pointer check] to use the objects from array.
I do not want the client to do that. But, if I use reference instead of pointer as a return type of operator[] I will not be able to return types which do not have copy constructor and I will return trash if nothing was put there before.
It seems like in Java and C# the problem is not fixed as well. The user is getting a reference to an object there and definitely should check for null being returned. And [indirection operator *] is called automatically there.

if you increment an iterator assigned to a pointer, will the pointer hold the original memory location?

My code keeps crashing and I believe it's because as i loop backwards in the insert function for Vector class, I decrement the iterator past the original pointer variable. Here's the insert function:
iterator insert(iterator & iter, const Object& obj){
if (theSize >= theCapacity){
resize(theSize+1);
int *p = iter;
for (iter; iter != this->end(); iter++){
//cout << "test1" << endl;
}
for (iter; iter != p; iter--){
*(iter-1) = *(iter-2);
cout << "test1" << endl;
//cout << *(iter - 2) << endl;
//cout << *(iter - 1) << endl;
}
}
else{
int *p = iter;
for (iter; iter != this->end(); iter++){
cout << "test" << endl;
}
for (iter; iter != p; iter--){
*(iter-1) = (*iter-2);
}
}
*iter = obj;
cout << theSize << endl << theCapacity << endl;
//theSize++;
return this->begin();
}
The goal of the insert function is to insert the object to the iterator position and in my code, i make sure that the Vector array is long enough and then I move every object in the array to the next indexed-space; and then I insert the object to the position designated by the iterator.
Also the entire Vector class is this:
#ifndef VECTOR_H
#define VECTOR_H
#include <algorithm>
#include <iostream>
template <typename Object>
class Vector
{
public:
explicit Vector(int initSize = 0)
: theSize{ initSize }, theCapacity{ initSize + SPARE_CAPACITY }
{
objects = new Object[theCapacity];
}
Vector(const Vector & rhs)
: theSize{ rhs.theSize }, theCapacity{ rhs.theCapacity }, objects{ nullptr }
{
objects = new Object[theCapacity];
for (int k = 0; k < theSize; ++k)
objects[k] = rhs.objects[k];
}
Vector & operator= (const Vector & rhs)
{
Vector copy = rhs;
std::swap(*this, copy);
return *this;
}
~Vector()
{
delete[] objects;
}
Vector(Vector && rhs)
: theSize{ rhs.theSize }, theCapacity{ rhs.theCapacity }, objects{ rhs.objects }
{
rhs.objects = nullptr;
rhs.theSize = 0;
rhs.theCapacity = 0;
}
Vector & operator= (Vector && rhs)
{
std::swap(theSize, rhs.theSize);
std::swap(theCapacity, rhs.theCapacity);
std::swap(objects, rhs.objects);
return *this;
}
bool empty() const
{
return size() == 0;
}
int size() const
{
return theSize;
}
int capacity() const
{
return theCapacity;
}
Object & operator[](int index)
{
return objects[index];
}
const Object & operator[](int index) const
{
return objects[index];
}
void resize(int newSize)
{
if (newSize > theCapacity)
reserve(newSize * 2);
theSize = newSize;
}
void reserve(int newCapacity)
{
if (newCapacity < theSize)
return;
Object *newArray = new Object[newCapacity];
for (int k = 0; k < theSize; ++k)
newArray[k] = std::move(objects[k]);
theCapacity = newCapacity;
std::swap(objects, newArray);
delete[] newArray;
}
// Stacky stuff
void push_back(const Object & x)
{
if (theSize == theCapacity)
reserve(2 * theCapacity + 1);
objects[theSize++] = x;
}
// Stacky stuff
void push_back(Object && x)
{
if (theSize == theCapacity)
reserve(2 * theCapacity + 1);
objects[theSize++] = std::move(x);
}
void pop_back()
{
--theSize;
}
const Object & back() const
{
return objects[theSize - 1];
}
// Iterator stuff: not bounds checked
typedef Object * iterator;
typedef const Object * const_iterator;
iterator begin()
{
return &objects[0];
}
const_iterator begin() const
{
return &objects[0];
}
iterator end()
{
return &objects[size()];
}
const_iterator end() const
{
return &objects[size()];
}
static const int SPARE_CAPACITY = 2;
iterator insert(iterator & iter, const Object& obj){
if (theSize >= theCapacity){
resize(theSize+1);
int *p = iter;
for (iter; iter != this->end(); iter++){
//cout << "test1" << endl;
}
for (iter; iter != p; iter--){
*(iter-1) = *(iter-2);
cout << "test1" << endl;
//cout << *(iter - 2) << endl;
//cout << *(iter - 1) << endl;
}
}
else{
int *p = iter;
for (iter; iter != this->end(); iter++){
cout << "test" << endl;
}
for (iter; iter != p; iter--){
*(iter-1) = (*iter-2);
}
}
*iter = obj;
cout << theSize << endl << theCapacity << endl;
//theSize++;
return this->begin();
}
iterator erase(iterator iter){
}
iterator find(iterator x, iterator y, const Object obj){
}
private:
int theSize;
int theCapacity;
Object * objects;
};
#endif
And my test file is this:
#include "Vector.h"
#include <iostream>
using namespace std;
int main(){
Vector<int> input;
Vector<int>::iterator iter;
int data = 0;
cout << "Enter five int digits: " << endl;
for (int i = 0; i < 5; i++){
cin >> data;
input.push_back(data);
}
data = 7654;
iter = input.begin();
iter++;
input.insert(iter, data);
for (iter = input.begin(); iter != input.end(); iter++){
cout << *iter << endl;
}
system("PAUSE");
}
Thanks to user4581301 and Igor's comments, I was able to solve it. You have to find the index of the iterator before it is lost when the array is resized. After resizing, set the iterator to the memory address of the object at the index. Like this:
if (theSize >= theCapacity){
int index = iter - this->begin();
resize(theSize+1);
iter = &objects[index];
int *p = iter;