I am working on a custom vector (String_vector) based off of a custom string class (String). I am having trouble with my copy assignment, and I think I am in some sort of loop that doesn't terminate. In my implementation I return *this as shown here, where I implement the copy-and-swap method.
String class:
class String{
public:
int len;
char *str;
String()
:len(0), str(nullptr){}
String(char const* S)
:len(strlen(S)), str(new char[len +1]){
assert(S != 0);
strcpy(str, S);
}
~String(){
delete[]str;
}
};
String_vector class:
class String_vector{
public:
String_vector(std::initializer_list<String>);
String* base = nullptr;
String* last = nullptr;
String* limit = nullptr;
String_vector(){};
String_vector(const String_vector& v){
reserve(v.size());
base = last;
last = uninitialized_copy(v.base, v.last, base);
}
//I think the issue is somewhere in here
String_vector& operator=(const String_vector & v){
String_vector p = v;
swap(*this, p);
return *this;
}
~String_vector(){
clear();
deallocate(base);
}
void clear(){
while(last > base){
(--last)->~String();
}
}
size_t size()const{
return last - base;
}
void swap(String_vector & v1, String_vector & v2){
std::swap(v1.base, v2.base);
std::swap(v1.last, v2.limit);
std::swap(v1.limit, v2.limit);
}
void reserve(std::size_t n){
if(!base){
base = allocate<String>(n);
last = base;
limit = n + base;
}else{
String* p = allocate<String>(n);
String* q = p;
for(String*i = base; i != last; ++i){
new(q)String(*i);
++q;
}
for(String*i = base; (i=last); ++i){
i ->~String();
}
deallocate<String>(base);
base = p;
last = q;
limit = base + n;
}
}
template<typename T>
inline T*
allocate(int n)
{
return reinterpret_cast<T*>(::operator new(n * sizeof(T)));
}
template<typename T>
inline void
deallocate(T* p)
{
::operator delete(p);
}
template<typename T, typename... Args>
inline T*
construct(T* p, Args&&... args)
{
return new (p) T(std::forward<Args>(args)...);
}
template<typename T>
inline void
destroy(T* p)
{
p->~T();
}
template<typename T>
inline T*
uninitialized_copy(T const* first, T const* last, T* out)
{
while (first != last) {
construct(out, *first);
++out;
++first;
}
return out;
}
size_t capacity()const{
return limit - base;
}
void push_back(String const & s){
if(!base){
reserve(8);
}else if(last == limit){
reserve(2*capacity());
}
construct(last++, s);
}
};
String_vector::String_vector(std::initializer_list<String> list)
: base(), last(), limit()
{
reserve(list.size());
for (String const& s : list)
push_back(s);
}
and main:
int main()
{
String_vector v1 {"a", "b", "c"};
String_vector v2;
v2 = v1;
return 0;
}
Thanks!
Related
I am implementing a kind of dataframe and I want to define a RandomAccessIterator over it, in order to execute the different std algorithms, such as the sorting one. The dataframe of the example contains two column "a" and "b":
a; b;
20; 21;
20; 19;
10; 11;
40; 41;
10; 11;
After sorting with a trivial selection sort this is the result:
a; b;
10; 11;
10; 11;
20; 19;
20; 21;
40; 41;
The problem that I am facing is that the std::sort does not work properly. And I don't know weather the implementation of the iterator is sound or not.
This is the code.
File: dataframe.hpp
#pragma once
#include <iostream>
#include <charconv>
#include <vector>
#include <memory>
#include <cstring>
#include <numeric>
#include "iterator.hpp"
namespace df
{
class Record;
class Column;
class Dataframe;
namespace types
{
enum class Base : char
{
CHAR = 'A',
UNSIGNED = 'U',
// Other types..
};
class Dtype
{
public:
Dtype(types::Base base, std::size_t size) : m_base_dtype{base}, m_size{size} {}
[[nodiscard]] auto name() const
{
return std::string{static_cast<char>(m_base_dtype)} + std::to_string(m_size);
}
[[nodiscard]] auto base() const { return m_base_dtype; }
[[nodiscard]] auto size() const { return m_size; }
[[nodiscard]] auto is_primitive() const
{
switch (base())
{
case types::Base::CHAR:
return size() == 1;
case types::Base::UNSIGNED:
return size() == 1 or size() == 2 or size() == 4 or size() == 8;
}
return false;
}
private:
types::Base m_base_dtype;
std::size_t m_size;
};
[[nodiscard]] static auto CHAR(const std::size_t size) { return Dtype(types::Base::CHAR, size); }
[[nodiscard]] static auto UNSIGNED(const std::size_t size) { return Dtype(types::Base::UNSIGNED, size); }
}
class Column
{
public:
Column(std::vector<char> &raw, const types::Dtype dtype) : m_raw{std::move(raw)}, m_dtype{dtype} {}
Column &operator=(Column &&c) = default; // Move constructor
[[nodiscard]] const auto &dtype() const { return m_dtype; }
[[nodiscard]] auto &raw() { return m_raw; }
[[nodiscard]] const auto &raw() const { return m_raw; }
[[nodiscard]] auto *data() { return m_raw.data(); }
[[nodiscard]] const auto *data() const { return m_raw.data(); }
private:
std::vector<char> m_raw;
types::Dtype m_dtype;
};
class Dataframe
{
public:
Dataframe(std::vector<char> &raw, std::vector<std::string> names, std::vector<types::Dtype> dtypes)
{
m_raw = std::move(raw);
m_column_dtypes = dtypes;
m_column_names = names;
m_record_size = 0;
for (const auto dt : dtypes)
{
m_column_offsets.emplace_back(m_record_size);
m_record_size += dt.size();
}
m_record_count = m_raw.size() / m_record_size;
}
Dataframe(std::vector<char> &raw, std::vector<types::Dtype> dtypes) : Dataframe(raw, {}, dtypes) {}
Dataframe &operator=(Dataframe &&c) = default; // Move constructor
[[nodiscard]] auto &raw() { return m_raw; }
[[nodiscard]] const auto &raw() const { return m_raw; }
[[nodiscard]] auto *data() { return m_raw.data(); }
[[nodiscard]] const auto *data() const { return m_raw.data(); }
// Iterators
[[nodiscard]] df::Iterator begin()
{
return df::Iterator{m_raw.data(), m_record_size};
}
[[nodiscard]] df::Iterator end()
{
return df::Iterator{m_raw.data() + m_raw.size(), m_record_size};
}
[[nodiscard]] auto shape() const { return std::make_pair(m_record_count, m_column_dtypes.size()); }
[[nodiscard]] auto record_count() const { return m_record_count; }
[[nodiscard]] auto record_size() const { return m_record_size; }
[[nodiscard]] const auto &names() const { return m_column_names; }
[[nodiscard]] const auto &dtypes() const { return m_column_dtypes; }
[[nodiscard]] const auto &offsets() const { return m_column_offsets; }
void print() { print(m_record_count); }
void print(const std::size_t initial_records)
{
// Print header
for (auto column_name : m_column_names)
{
std::cout << column_name << "; ";
}
std::cout << std::endl;
// Print rows
std::size_t records_to_print = std::min(initial_records, m_record_count);
for (std::size_t i = 0; i < records_to_print; i++)
{
const auto start_p = i * record_size();
auto start_field = 0;
auto end_field = 0;
for (auto field : m_column_dtypes)
{
end_field += field.size();
switch (field.base())
{
case types::Base::UNSIGNED:
{
std::uint64_t uint_value = 0;
memcpy(&uint_value, m_raw.data() + start_p + start_field, field.size());
std::cout << uint_value;
break;
}
case types::Base::CHAR:
{
std::string str_value = std::string(m_raw.data() + start_p + start_field, field.size());
std::cout << str_value;
break;
}
}
start_field = end_field;
// New column
std::cout << "; ";
}
// New row
std::cout << std::endl;
}
}
std::shared_ptr<Dataframe> copy() const
{
auto x = std::vector<char>(m_raw);
return std::make_shared<Dataframe>(x, std::vector<std::string>(m_column_names), std::vector<types::Dtype>(m_column_dtypes));
}
private:
std::vector<char> m_raw = {};
std::vector<std::string> m_column_names = {};
std::vector<types::Dtype> m_column_dtypes = {};
std::vector<std::size_t> m_column_offsets = {};
std::size_t m_record_size = {};
std::size_t m_record_count = {};
};
using namespace types;
static std::shared_ptr<Dataframe> read_from_vector(const std::vector<std::vector<std::string>> values, const std::vector<std::string> names, const std::vector<Dtype> dtypes)
{
const auto record_size = std::accumulate(dtypes.begin(), dtypes.end(), std::size_t{0},
[](std::size_t accum, const auto &m)
{ return accum + m.size(); });
const auto total_size = values.size() * record_size;
const std::size_t INCR_RECORDS = std::max(total_size / (10 * record_size), std::size_t{65536});
auto raw = std::vector<char>{};
std::size_t written_records = 0;
auto offsets = std::vector<std::size_t>{};
for (int offset = 0; const auto &kd : dtypes)
{
offsets.push_back(offset);
offset += kd.size();
}
for (auto value : values)
{
if (written_records >= raw.size() / record_size)
{
raw.resize(raw.size() + INCR_RECORDS * record_size, char{' '});
}
for (int i = 0; i < names.size(); i++)
{
const auto name = names[i];
const auto dtype = dtypes[i];
const auto offset = offsets[i];
const auto pos = written_records * record_size + offset;
switch (dtype.base())
{
case df::Base::CHAR:
{
const auto v = value[i];
const auto byte_to_copy = std::min(v.size(), dtype.size());
std::memcpy(raw.data() + pos,
v.data() + v.size() - byte_to_copy, byte_to_copy); // Prendo gli ultimi byte
break;
}
case df::Base::UNSIGNED:
{
const auto v = std::stoull(value[i]);
const auto byte_to_copy = dtype.size();
std::memcpy(raw.data() + pos, &v, byte_to_copy); // Prendo gli ultimi byte
break;
}
default:
throw std::runtime_error("ColumnType non riconosciuto");
}
}
written_records++;
}
raw.resize(written_records * record_size);
raw.shrink_to_fit();
return std::make_shared<Dataframe>(raw, names, dtypes);
}
}
File: iterator.hpp
#pragma once
#include <iostream>
#include <cstring>
namespace df
{
class Iterator
{
std::size_t size;
char *ptr;
public:
struct record_reference;
struct record_value
{
std::size_t size;
char *ptr;
record_value(const record_reference &t) : record_value(t.size, t.ptr){};
record_value(const std::size_t m_size, char *m_ptr)
{
this->size = m_size;
this->ptr = new char[this->size];
std::memcpy(ptr, m_ptr, this->size);
}
~record_value()
{
delete[] this->ptr;
}
};
struct record_reference
{
std::size_t size;
char *ptr;
record_reference(const std::size_t m_size, char *m_ptr)
{
this->size = m_size;
this->ptr = m_ptr;
}
record_reference(const record_reference &t)
{
this->size = t.size;
this->ptr = t.ptr;
}
// record_reference(const record_value &t) : record_reference(t.size, t.ptr) {};
record_reference &operator=(const record_value &t)
{
std::memcpy(ptr, t.ptr, size);
return *this;
}
record_reference &operator=(const record_reference &t)
{
std::memcpy(ptr, t.ptr, size);
return *this;
}
record_reference &operator=(char *t)
{
std::memcpy(ptr, t, size);
return *this;
}
operator char *()
{
return ptr;
}
operator const char *() const { return ptr; }
};
using iterator_category = std::random_access_iterator_tag;
using value_type = record_value;
using reference = record_reference;
using difference_type = std::ptrdiff_t;
// default constructible
Iterator() : size(0), ptr(nullptr)
{
}
// copy assignable
Iterator &operator=(const Iterator &t)
{
size = t.size;
ptr = t.ptr;
return *this;
}
Iterator(char *ptr, const std::size_t size) : size{size}, ptr(ptr)
{
}
record_reference operator*() const
{
return {size, ptr};
}
// Prefix
Iterator &operator++()
{
ptr += size;
return *this;
}
// Postfix
Iterator operator++(int)
{
auto tmp = *this;
++*this;
return tmp;
}
Iterator &operator--()
{
ptr -= size;
return *this;
}
difference_type operator-(const Iterator &it) const
{
return (this->ptr - it.ptr) / size;
}
Iterator operator+(const difference_type &offset) const
{
return Iterator(ptr + offset * size, size);
}
friend Iterator operator+(const difference_type &diff, const Iterator &it)
{
return it + diff;
}
Iterator operator-(const difference_type &diff) const
{
return Iterator(ptr - diff * size, size);
}
reference operator[](const difference_type &offset) const
{
return {size, ptr + offset * size};
}
bool operator==(const Iterator &it) const
{
return this->ptr == it.ptr;
}
bool operator!=(const Iterator &it) const
{
return !(*this == it);
}
bool operator<(const Iterator &it) const
{
return this->ptr < it.ptr;
}
bool operator>=(const Iterator &it) const
{
return this->ptr >= it.ptr;
}
bool operator>(const Iterator &it) const
{
return this->ptr > it.ptr;
}
bool operator<=(const Iterator &it) const
{
return this->ptr <= it.ptr;
}
Iterator &operator+=(const difference_type &diff)
{
ptr += diff * size;
return *this;
}
operator Iterator() const
{
return Iterator(ptr, size);
}
};
void swap(df::Iterator::record_reference a, df::Iterator::record_reference b)
{
unsigned char *p;
unsigned char *q;
unsigned char *const sentry = (unsigned char *)a.ptr + a.size;
for (p = (unsigned char *)a.ptr, q = (unsigned char *)b.ptr; p < sentry; ++p, ++q)
{
const unsigned char t = *p;
*p = *q;
*q = t;
}
}
}
File: comparator.hpp
#pragma once
#include <memory>
#include <functional>
#include "dataframe.hpp"
#include "iterator.hpp"
namespace compare
{
using comparator_fn = std::function<int(const df::Iterator::record_reference, const df::Iterator::record_reference)>;
template <typename T, std::size_t offset = 0, std::size_t size = sizeof(T)>
static inline comparator_fn make_comparator()
{
if constexpr (size == 3 or size == 5 or size == 7 or size > 8)
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return std::memcmp(a + offset, b + offset, size); };
return [](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return *(T *)(a + offset) < *(T *)(b + offset) ? -1 : *(T *)(b + offset) < *(T *)(a + offset) ? +1
: 0; };
}
template <typename T>
static inline comparator_fn make_comparator(const std::size_t offset)
{
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return *(T *)(a + offset) < *(T *)(b + offset) ? -1 : *(T *)(b + offset) < *(T *)(a + offset) ? +1
: 0; };
}
static inline comparator_fn make_column_comparator(const df::Dtype dtype, const std::size_t offset)
{
switch (dtype.base())
{
case df::Base::CHAR:
{
if (dtype.size() == 1)
return make_comparator<std::uint8_t>(offset);
else if (dtype.size() == 2)
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return std::memcmp(a + offset, b + offset, 2); }; // C'� qualche beneficio a fissare il 2? o conviene trattarlo come uno unsigned short?
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return std::memcmp(a + offset, b + offset, dtype.size()); };
}
case df::Base::UNSIGNED:
{
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{
std::uint64_t uint_value_a = 0;
std::uint64_t uint_value_b = 0;
std::memcpy(&uint_value_a, a + offset, dtype.size());
std::memcpy(&uint_value_b, b + offset, dtype.size());
return (uint_value_a < uint_value_b ? -1 : uint_value_a > uint_value_b ? +1
: 0);
};
}
default:
throw std::runtime_error("Unsupported dtype");
break;
}
}
static inline comparator_fn make_composite_two_way_comparator(const std::shared_ptr<df::Dataframe> &T)
{
const auto K = T->dtypes().size();
std::vector<comparator_fn> F;
for (int i = 0; i < K; i++)
{
F.emplace_back(make_column_comparator(T->dtypes()[i], T->offsets()[i]));
}
const auto comparator = [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{
for (int i = 0; i < K; i++)
{
// If equal go to the next column, otherwise return the result
// The return value is true if the first argument is less than the second
// and false otherwise
if (const auto result = F[i](a, b); result != 0)
return result < 0;
}
return false;
};
return comparator;
}
}
File: main.cpp
#include <iostream>
#include <vector>
#include "dataframe.hpp"
#include "comparator.hpp"
template <typename RandomAccessIterator, typename Comparator>
static void selection_sort(RandomAccessIterator first, RandomAccessIterator last, Comparator comp)
{
for (auto i = first; i != last; ++i)
{
auto min = i;
for (auto j = i + 1; j != last; ++j)
{
if (comp(*j, *min))
min = j;
}
df::Iterator::value_type temp = *i;
*i = *min;
*min = temp;
// Alternative
// std::iter_swap(i, min);
}
}
int main(int argc, char const *argv[])
{
std::vector<std::string> values{"20", "21", "20", "19", "10", "11", "40", "41", "10", "11"};
// Create a vector that contains values grouped by 2
std::vector<std::vector<std::string>> v;
for (int i = 0; i < values.size(); i += 2)
{
std::vector<std::string> temp;
temp.push_back(values[i]);
temp.push_back(values[i + 1]);
v.push_back(temp);
}
std::vector<std::string> column_names = {"a", "b"};
df::Dtype d = df::Dtype(df::Base::UNSIGNED, 4);
std::vector dtypes = {d, d};
// Create a dataframe
std::shared_ptr<df::Dataframe> df = df::read_from_vector(v, column_names, dtypes);
std::cout << "Before sorting" << std::endl;
df->print();
// This comparator sorts the dataframe first by column a and then by column b in ascending order
auto comparator = compare::make_composite_two_way_comparator(df);
selection_sort(df->begin(), df->end(), comparator);
std::cout << "\nAfter sorting" << std::endl;
df->print();
// With the std::sort it does not work
std::sort(df->begin(), df->end(), comparator);
return 0;
}
Your type is not a C++17 RandomAccessIterator, because it isn't a C++17 ForwardIterator, because reference is an object type, not a reference type.
The type It satisfies ForwardIterator if
Let T be the value type of It. The type std::iterator_traits<It>::reference must be either
T& or T&& if It satisfies OutputIterator (It is mutable), or
const T& or const T&& otherwise (It is constant),
(Other requirements elided)
You will be able to satisfy the C++20 concept std::random_access_iterator, because that relaxes the requirement on It::reference.
In C++17, the reference type of an iterator must be precisely value_type& in order for that iterator to be random access. Only input iterators can have the reference type be something other than value_type&. So in C++17, proxy iterators are limited to input iterators. And every algorithm written against C++17 has this expectation.
The C++20 ranges library adds the ability to have random access proxy iterators. And the C++20 algorithms that use those range concepts will respect them.
Currently my class looks like this, by unit tests done, it works but my goal will be to implement as precise in question the lib Boost and especially the multi index container.
I tried to change the type of the items but it didn't work, like this:
typedef multi_index_container<items,indexed_by<sequenced<>,hashed_unique<identity<Item> >>> item_list;
I got a message: Invalid use of non-static data member '_items'. This kind of error I can understand it but i not specify it because it is the global implementation that worries me
If you find any other error apart from my precise question, I am also a taker.
template<typename Tkey, typename Tval>
class CacheWeb{
private:
unsigned int capacity;
std::list<std::pair<Tkey, Tval>> items;
std::unordered_map<key, typename std::list<std::pair<Tkey, Tval>>::iterator> lookup;
CacheWeb(const CacheWeb&) = delete;
CacheWeb& operator=(const CacheWeb&) = delete;
int capacityOut(){
if( capacity == 0 || lookup.size() < capacity ) {
return 0;
}
int cnt = 0;
while(lookup.size() > capacity) {
lookup.erase(items.back().first);
items.pop_back();
++cnt;
}
return cnt;
};
public:
CacheWeb(int icapacity) : capacity(icapacity){};
virtual ~CacheWeb() = default;
int size(){
return lookup.size();
};
bool empty(){
return lookup.empty();
};
void clear(){
lookup.clear();
items.clear();
};
bool contains(const Tkey& key){
return lookup.find(key) != lookup.end();
};
void remove(const Tkey& key){
auto it = lookup.find(key);
items.erase(it->second);
lookup.erase(it);
};
void put(const Tkey& key, const Tval& val){
auto it = lookup.find(key);
if( it != lookup.end() ) {
it->second->second = val;
items.splice(items.begin(), items, it->second);
return;
}
items.emplace_front(key, val);
lookup[key] = items.begin();
capacityOut();
};
std::list<std::pair<Tkey, Tval>>getItems(){
return items;
};
const VAL_T& get(const Tkey& key){
const auto it = lookup.find(key);
if( it == lookup.end() ) {
throw std::invalid_argument("Key does not exist");
}
items.splice(items.begin(), items, it->second);
return it->second->second;
};
};
}
Something like this can do:
Live Coliru Demo
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/key.hpp>
template<typename Tkey, typename Tval>
class CacheWeb{
private:
using value_type = std::pair<Tkey, Tval>;
unsigned int capacity;
boost::multi_index_container<
value_type,
boost::multi_index::indexed_by<
boost::multi_index::sequenced<>,
boost::multi_index::hashed_unique<boost::multi_index::key<&value_type::first>>
>
> container;
CacheWeb(const CacheWeb&) = delete;
CacheWeb& operator=(const CacheWeb&) = delete;
int capacityOut(){
if( capacity == 0 || container.size() < capacity ) {
return 0;
}
int cnt = 0;
while(container.size() > capacity) {
container.pop_back();
++cnt;
}
return cnt;
};
public:
CacheWeb(int icapacity) : capacity(icapacity){};
virtual ~CacheWeb() = default;
int size(){
return container.size();
};
bool empty(){
return container.empty();
};
void clear(){
container.clear();
};
bool contains(const Tkey& key){
const auto& lookup = container.template get<1>();
return lookup.find(key) != container.template get<1>().end();
};
void remove(const Tkey& key){
container.erase(key);
};
void put(const Tkey& key, const Tval& val){
auto& lookup = container.template get<1>();
auto it = lookup.find(key);
if( it != lookup.end() ) {
lookup.modify(it,[&](value_type& x){ x.second = val; });
}
else{
it=lookup.emplace(key, val).first;
}
container.relocate(container.begin(),container.template project<0>(it));
capacityOut();
};
std::list<std::pair<Tkey, Tval>>getItems(){
return {container.begin(), container.end()};
};
const Tval& get(const Tkey& key){
const auto& lookup = container.template get<1>();
const auto it = lookup.find(key);
if( it == lookup.end() ) {
throw std::invalid_argument("Key does not exist");
}
return it->second;
}
};
#include <iostream>
int main()
{
CacheWeb<int,int> c(10);
for(int i=0;i<11;++i)c.put(i,i);
for(const auto& x:c.getItems()){
std::cout<<"("<<x.first<<","<<x.second<<")";
}
std::cout<<"\n";
for(int i=1;i<11;++i){
std::cout<<i<<"->"<<c.get(i)<<" ";
}
std::cout<<"\n";
}
Output
(10,10)(9,9)(8,8)(7,7)(6,6)(5,5)(4,4)(3,3)(2,2)(1,1)
1->1 2->2 3->3 4->4 5->5 6->6 7->7 8->8 9->9 10->10
I'm very very new to vectors and templates. Can't seem to wrap my head around them just yet.
The code is as follows:
template <class T>
class m_vector
{
int m_iNextIndex;
int m_iMaxSize;
T* m_pArray;
public:
m_vector()
{
m_pArray = 0;
init();
}
m_vector(const m_vector<T>& other)
{
m_pArray = 0;
m_iNextIndex = 0;
m_iMaxSize = 0;
*this = other;
}
~m_vector()
{
if (m_pArray != 0)
delete [] m_pArray;
}
void init()
{
m_iMaxSize = VECT_INC_SIZE;
m_pArray = new T[m_iMaxSize];
m_iNextIndex = 0;
}
inline void push_back(const T& item)
{
if (m_iNextIndex >= m_iMaxSize)
{
resize(m_iNextIndex + VECT_INC_SIZE);
m_iNextIndex -= VECT_INC_SIZE;
}
m_pArray[m_iNextIndex] = item;
++m_iNextIndex;
}
void resize(int iNewSize)
{
if (iNewSize >= m_iMaxSize)
{
T* temp = new T[iNewSize];
for (int i = 0; i < m_iNextIndex; ++i)
{
temp[i] = m_pArray[i];
}
delete [] m_pArray;
m_pArray = temp;
m_iMaxSize = iNewSize;
}
m_iNextIndex = iNewSize;
}
};
The push_back is called with Vectors that are (x, y) and rectangles that are (x, y, width, height).
I thought that since the code had "m_pArray[m_iNextIndex] = item" that I could simply add a new function to check that the item is unique before adding it to the vector list. So I coded a routine as follows:
int inline exists(const &item)
{
for(int i = 0; i < m_iMaxSize; i++)
{
if(m_pArray[i] == item)
return 1;
}
return 0;
}
The compiler was non to happy. It reported the following error on the if statement:
“left operand must be a pointer or pointer to class member, or arithmetic”
I'm thinking that I probably need something like:
if (type == vector)
if( (m_pArray[i].x == item.x) ....)
else
if( (m_pArray[i].x == item.x) && (m_pArray[i].width == item.width) )
Am I on the right track?
If so, how do I check the type of item.
Thx, Bill.
The problem is that your Vector or Rectangle are missing a comparison operator e.g.
friend bool operator== (Vector const& lhs, Vector const& rhs){
return lhs.x == rhs.x && lhs.y == rhs.y;
}
But the rest of you code is not good and needs a rewrite. I've rewritten my code to old-old style:
NOTE: DON'T BLINDLY COPY THIS CODE. ITS JUST FOR REFERENCE. TEST BEFORE YOU USE
#define VECT_INC_SIZE 10
// replaced by iterator version
//template< class InputIt, class Size, class OutputIt>
//OutputIt copy_n(InputIt first, Size count, OutputIt result) {
// while (count-- > 0) {
// *result++ = *first++;
// }
// return result;
//}
template<class InputIt, class OutputIt>
OutputIt copy(InputIt first, InputIt last, OutputIt d_first) {
while (first != last) {
*d_first++ = *first++;
}
return d_first;
}
template<class T>
T exchange(T& obj, T new_value)
{
T old_value = obj;
obj = new_value;
return old_value;
}
template <class T>
class m_vector
{
private:
unsigned int size;
unsigned int capacity;
T* array;
public:
m_vector()
: size(0)
, capacity(VECT_INC_SIZE)
, array(new T[VECT_INC_SIZE])
{}
m_vector(m_vector<T> const& other)
: size(other.size)
, capacity(other.capacity)
, array(new T[other.capacity]) {
//copy_n(other.array, size, array);
copy(other.cbegin(), other.cend(), begin());
}
// rule of 3
m_vector& operator=(m_vector<T> const& other) {
if (&other != this) {
if (other.capacity != capacity){
delete [] exchange(array, new T[other.capacity]);
capacity = other.capacity;
}
size = other.size;
//copy_n(other.array, size, array);
copy(other.cbegin(), other.cend(), begin());
}
return *this;
}
~m_vector() {
if (array != 0) delete [] array;
}
void push_back(T const& item) {
if (size >= capacity) {
reserve(capacity + VECT_INC_SIZE);
}
array[size] = item;
++size;
}
void reserve(unsigned int newCapacity) {
if (size > capacity) {
T* const temp = new T[newCapacity];
//copy_n(array, size, temp);
copy(cbegin(), cend(), temp);
delete [] exchange(array, temp);
capacity = newCapacity;
}
}
bool exists(T const& item) const {
//for(unsigned int i = 0; i < size; ++i) {
// if(array[i] == item) return true;
//}
for(T const* it = cbegin(); it != cend(); ++it) {
if(*it == item) return true;
}
return false;
}
void erase(T const& item) {
T* it = begin();
T* const endIt = end();
while(it != endIt && *it != item) ++it;
if (it == endIt) return;
copy(it + 1, endIt, it);
--size;
}
// iterators
T* begin() { return array; }
T* end() { return array + size; }
T const* cbegin() const { return array; }
T const* cend() const { return array + size; }
};
class Vector {
private:
int x,y;
public:
Vector() : x(0), y(0) {}
Vector(int x, int y) : x(x), y(y) {}
friend bool operator== (Vector const& lhs, Vector const& rhs){
return lhs.x == rhs.x && lhs.y == rhs.y;
}
friend bool operator!= (Vector const& lhs, Vector const& rhs){
return !(lhs==rhs);
}
};
#include <cstdio>
void VectorInM_Vector(m_vector<Vector> const& vec, Vector const& item) {
if (vec.exists(item)) printf("Vector found in vector\n");
else printf("Vector not found in vector\n");
}
int main(){
m_vector<Vector> vec;
vec.push_back(Vector(1,1));
VectorInM_Vector(vec, Vector(1,1));
m_vector<Vector> vec2(vec);
vec.erase(Vector(1,1));
VectorInM_Vector(vec, Vector(1,1));
VectorInM_Vector(vec2, Vector(1,1));
vec2 = vec;
VectorInM_Vector(vec2, Vector(1,1));
}
on compiler explorer/godbolt
So i have a custom made vector class with also an custom allocator:
#define _SCL_SECURE_NO_WARNINGS
#include <iostream>
#include <exception>
#include <sstream>
#include <string>
#include <iostream>
namespace mem {
template<typename T>
class allocator {
public:
//
T * allocate(int n); //allocate space for n objects of type T
void deallocate(T* p); //deallocate n objects of type T starting at p
void construct(T* p, const T& v); // construct a T with the value v in p
void destroy(T* p); // destroy the T in p
};
template<typename T> T* allocator<T>::allocate(int n) //allocate space for n objects of type T
{
T* res = (T*)malloc(sizeof(T)*n);
if (res == nullptr)
throw std::bad_alloc();
return res;
}
template<typename T> void allocator<T>::deallocate(T* p/*, int n*/) //deallocate n objects of type T starting at p
{
free(p);
}
template<typename T> void allocator<T>::construct(T* p, const T& v) // construct a T with the value v in p
{
new(p) T(v);
}
template<typename T> void allocator<T>::destroy(T* p) // destroy the T in p
{
p->~T();
}
}
namespace vec {
template<typename T, typename A = mem::allocator<T>>
class vector {
A alloc; //use allocate to handle memory for elements
int sz; //the size
T* elem; //a pointer to the elements
int space; //size + free space
public:
using size_type = unsigned long;
using value_type = T;
using iterator = T * ;
using const_iterator = const T*;
vector() :sz{ 0 }, elem{ nullptr }, space{ 0 } {}
explicit vector(int s) :sz{ s }, elem{ new T[s] }, space{ s }
{
for (int i = 0; i < sz; ++i) elem[i] = 0; // elements are initalized
}
vector(const vector& arg); //copy constructor
vector& operator =(const vector& a); //copy assignment
vector(vector&& a); //move constructor
vector& operator=(vector&& a); //move assignment
~vector() { delete[] elem; } //destructor
void reserve(int newalloc);
void resize(int newsize, T val = T()); //growth
void push_back(const T& val);
iterator begin() { return elem; }
const_iterator begin()const { return elem; }
iterator end() { return elem + sz; }
const_iterator end() const { return elem + sz; }
size_type size() { return sz; }
iterator back() { return end() - 1; }
const_iterator back() const { return end() - 1; }
};
template<typename T, typename A> vector<T, A>::vector(const vector<T, A>& a) //copy constructor
:sz{ a.sz }, elem{ new T[a.sz] }
{
copy(a.elem, a.elem + sz, elem);
}
template<typename T, typename A> vector<T, A>& vector<T, A>::operator =(const vector<T, A>& a) //copy assignment
{
if (this == &a) return *this; //return if self assignment
if (a.sz <= space) { //enough space no need for new allocation
for (int i = 0; i < a.sz; ++i) //copy elements
alloc.construct(&elem[i], a.elem[i]);
sz = a.sz;
return *this;
}
T* p = alloc.allocate(a.sz);//allocate new size
for (int i = 0; i < a.sz; ++i) //copy elements
alloc.construct(&p[i], a.elem[i]);
for (int i = 0; i < sz; ++i)
alloc.destroy(&elem[i]); //destroy
alloc.deallocate(elem);
space = sz = a.sz; //set new size
elem = p; //set new elements
return *this; //return a self reference
}
template<typename T, typename A> vector<T, A>::vector(vector<T, A>&& a) //move constructor
:sz{ a.sz }, elem{ a.elem }
{
a.sz = 0; //make a the empty vector
a.elem = nullptr;
}
template<typename T, typename A> vector<T, A>& vector<T, A>::operator=(vector<T, A>&& a) //move assignment
{
delete[] elem; //deallocate old space
elem = a.elem; //copy a's elem and sz
sz = a.sz;
a.elem = nullptr; //make a the empty vector
a.sz = 0;
return *this;
}
template<typename T, typename A> void vector<T, A>::reserve(int newalloc)
{
if (newalloc <= space) return; //never decrease allocation
T* p = alloc.allocate(newalloc); //alocate new space
for (int i = 0; i < sz; ++i)
alloc.construct(&p[i], elem[i]); //copy
for (int i = 0; i < sz; ++i)
alloc.destroy(&elem[i]); //destroy
alloc.deallocate(elem/*, space*/); //deallocate old space
elem = p;
space = newalloc;
}
template<typename T, typename A> void vector<T, A>::push_back(const T& val)
{
if (space == 0)
reserve(8);
else if (space == sz)
reserve(2 * space);
alloc.construct(&elem[sz], val); //add val at the end
++sz;
}
template<typename T, typename A> void vector<T, A>::resize(int newsize, T val)
{
reserve(newsize);
for (int i = sz; i < newsize; ++i)
alloc.construct(&elem[i], val); //construct
for (int i = newsize; i < sz; ++i)
alloc.destroy(&elem[i]); //destroy
sz = newsize;
}
template<typename T, typename A> std::ostream& operator<<(std::ostream& os, const vector<T, A>& obj) //requires Element<T> && Allocator<A>
{
for (const auto& x : obj)
os << x << ' ';
os << '\n';
return os;
}
template<typename T, typename A> std::istream& operator>>(std::istream& is, vector<T, A>& obj)
{
std::string line;
std::getline(is, line);
if (is.bad() || is.fail())
return is;
std::istringstream istr{ line };
vector<T, A> tmp;
while (true) {
T tmp_in = T();
istr >> tmp_in;
if (istr.fail()) {
istr.clear();
std::string invalid;
istr >> invalid;
continue;
}
tmp.push_back(tmp_in);
if (istr.eof())break;
}
for (const auto& x : tmp)
obj.push_back(x);
return is; //its sends me to xutility in this step
}
}
int main()
{
//vec::vector <int> test; //input "1 2 3 4 5" works fine with cin >>test
vec::vector <std::string> test; //input "this is a test" breaks into xutility
while (true) {
std::cin >> test; //breaks into xutility
std::cout << test << '\n';
}
}
Now the following problem. I can read in vector values if the vector is vector. So adding 1 2 3 4 5 is no problem.
Also vector works fine.
If i change the container type to vector and read in "hello this is a test" via cin >> test; I get a crahs in xutility.h:
// MEMBER FUNCTIONS FOR _Container_base12
inline void _Container_base12::_Orphan_all() _NOEXCEPT
{ // orphan all iterators
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myproxy != 0) //It points on this line
{ // proxy allocated, drain it
_Lockit _Lock(_LOCK_DEBUG);
for (_Iterator_base12 **_Pnext = &_Myproxy->_Myfirstiter;
*_Pnext != 0; *_Pnext = (*_Pnext)->_Mynextiter)
(*_Pnext)->_Myproxy = 0;
_Myproxy->_Myfirstiter = 0;
}
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
}
Visual Studio 2017 throws:
Exception thrown: read access violation.this was 0xC83FB858.
How can this header even get called and what is the meaning of it?
I really have no idea whats going on.
I replaced the custom vector with std::vector and then it also works.
edit: I minimized the code a bit and added a namespace for the vector. still the same behavior:
vec::vector<int> test;
std::cin >> test; //Ok: Example input 1 2 3 4 5
vec::vector<std::string> test;
std::cin >> test; //Mem access violation in xutility: Example input "this is a test"
The application crashes because:
The code does not use allocator consistently and ends up doing free() on memory that was allocated with new[].
The code does not maintain vector::space variable consistently.
I was trying my hand with allocators this time & felt that there were many chances of leaking the resources. So I thought what if I used std::unique_ptr to handle them. I tried my hand with a std::vector's allocator. My code goes like this :-
// allocator
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class X
{
int x,ID;
static int i;
public:
X()
{
cout<<"constructing ";
ID=++i;
cout<<"ID="<<ID<<'\n';
}
X(int a)
{
x=a;
cout<<"constructing ";
ID=++i;
cout<<"ID="<<ID<<'\n';
}
void get()
{
cout<<"enter x: ";
cin>>x;
}
void disp()
{
cout<<"x="<<x<<'\t';
}
~X()
{
cout<<"destroying ID="<<ID<<'\n';
}
};
int X:: i=0;
int main()
{
ios::sync_with_stdio(false);
vector<X> v;
auto alloc = v.get_allocator();
unsigned int i=0;
X *p(alloc.allocate(5));
for (i=0;i<5;++i)
alloc.construct (&p[i], i+1);
unique_ptr<X[]> ptr(p);
cout<<"\nthe elements are:-\n";
for (i=0;i<5;++i)
{
ptr[i].disp();
cout << '\t' << (long long)alloc.address(ptr[i]) << '\n';
}
cout<<"\n";
/*for (i=0;i<5;++i)
alloc.destroy(&p[i]);
deallocate(p,16)*/
return 0;
}
Unfortunately this code crashes showing UB. So what should I do ? How should I manipulate my code so as to make it suited for std::unique_ptr ?
template<typename T>
std::unique_ptr<T[], std::function<void(T *)>> make_T(X *ptr, std::allocator<T> alloc, std::size_t size) {
auto deleter = [](T *p, std::allocator<T> alloc, std::size_t size) {
for (int i = 0; i < size; ++i) {
alloc.destroy(&p[i]);
}
alloc.deallocate(p, sizeof(T) * size);
};
return {ptr, std::bind(deleter, std::placeholders::_1, alloc, size)};
}
int main(int argc, const char * argv[]) {
std::allocator<X> alloc = std::allocator<X>();
X *p = alloc.allocate(5);
for (int i = 0; i < 5; ++i) {
alloc.construct(&p[i], i + 1);
}
auto ptr = make_T(p, alloc, 5);
return 0;
}
Can also write one to construct the objects for you:
template<typename T, typename... Args>
std::unique_ptr<T[], std::function<void(T *)>> make_T_Construct(std::allocator<T> alloc, std::size_t size, Args... args) {
X *ptr = alloc.allocate(size);
for (std::size_t i = 0; i < size; ++i) {
alloc.construct(&ptr[i], std::forward<Args>(args)...);
}
auto deleter = [](T *p, std::allocator<T> alloc, std::size_t size) {
for (std::size_t i = 0; i < size; ++i) {
alloc.destroy(&p[i]);
}
alloc.deallocate(p, sizeof(T) * size);
};
return {ptr, std::bind(deleter, std::placeholders::_1, alloc, size)};
}
int main(int argc, const char * argv[]) {
std::allocator<X> alloc = std::allocator<X>();
auto ptr = make_T_Construct(alloc, 5, 100);
return 0;
}
Edit: To do what you want (tracking allocations), you have to track the memory allocations yourself using a custom allocator..
template<typename T>
struct Allocator
{
typedef T value_type;
Allocator() noexcept {};
template<typename U>
Allocator(const Allocator<U>& other) throw() {};
T* allocate(std::size_t n, const void* hint = 0)
{
T* memory = static_cast<T*>(::operator new(n * (sizeof(T) + sizeof(bool))));
for (std::size_t i = 0; i < n * (sizeof(T) + sizeof(bool)); ++i)
{
*reinterpret_cast<bool*>(reinterpret_cast<char*>(memory) + sizeof(bool)) = false;
}
return memory;
}
void deallocate(T* ptr, std::size_t n)
{
::operator delete(ptr);
}
void construct(T* p, const T& arg)
{
destroy(p);
new(p) T(arg);
*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = true;
}
template<class U, class... Args>
void construct(U* p, Args&&... args)
{
destroy(p);
::new(p) U(std::forward<Args>(args)...);
*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = true;
}
void destroy(T* p)
{
if (*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool))) {
p->~T();
*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = false;
}
}
template<class U>
void destroy(U* p)
{
if (*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool))) {
p->~U();
*reinterpret_cast<bool*>(reinterpret_cast<char*>(p) + sizeof(bool)) = false;
}
}
};
template <typename T, typename U>
inline bool operator == (const Allocator<T>&, const Allocator<U>&)
{
return true;
}
template <typename T, typename U>
inline bool operator != (const Allocator<T>& a, const Allocator<U>& b)
{
return !(a == b);
}