I am trying to create a custom allocator, but there are compilation problems.
By changing the value of the define from
#define _GLIBCXX_FULLY_DYNAMIC_STRING 0
to
#define _GLIBCXX_FULLY_DYNAMIC_STRING 1
I managed to switch from being unable to compile to being able to compile, but should it be like that? Shouldn't it be a little bit simpler ?
Does any body has experienc on that and already knows how these compilation problems may be solved.
The code the minimal required:
#include <bits/c++config.h>
#define _GLIBCXX_FULLY_DYNAMIC_STRING 0
#include <stdint.h>
#include <stddef.h>
#include <memory>
#include <string>
#include <limits>
typedef int32_t Token;
typedef unsigned char byte;
using namespace std;
template <typename T>
struct Allocator {
public:
static const size_t heapSize=0x1000;
static size_t freePos;
static Token freeT;
static byte m[heapSize];
// http://www.codeproject.com/Articles/4795/C-Standard-Allocator-An-Introduction-and-Implement
typedef T value_type;
typedef value_type* pointer;typedef const value_type* const_pointer;
typedef value_type& reference;typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template<typename U> struct rebind {typedef Allocator<U> other;};
inline explicit Allocator() {freeT=0;freePos=0;}
inline ~Allocator() {}
inline Allocator(Allocator const&) {} // with explicit it doesn't compile
//template<typename U>
//inline explicit Allocator(Allocator<U> const&) {}
inline pointer address(reference r) {return &r;}
inline const_pointer address(const_reference r) {return &r;}
static inline pointer allocate(size_type cnt, typename std::allocator<void>::const_pointer hint = 0){
return reinterpret_cast<pointer>(::operator new(cnt * sizeof (T)));
/* pointer allocate(size_type n, const_pointer hint = 0 ){
size_t t=freePos;freePos+=sizeof(T)*n;return t;
}
*/
}
static inline void deallocate(pointer p, size_type){
::operator delete(p);
/* pointer deallocate(pointer p,size_type n){
size_t sz=sizeof(T)*n;
*(size_t*)(m+p)=sz;
}
*/
}
inline size_type max_size() const{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
inline void construct(pointer p, const T& t) { new(p) T(t); }
/*
void construct(pointer p, const T& val)
{ new ((T*) p) T(val); }
*/
inline void destroy(pointer p) { p->~T(); }
// void destroy(pointer p) { ((T*)m[p])->~T(); }
inline bool operator==(Allocator const&) {return true;}
inline bool operator!=(Allocator const& a) {return false;}
};
#endif
using namespace std;
typedef std::basic_string< char,std::char_traits<char>,Allocator<char> > String;
int main(){
String s("Nice-the-data-goes-in-my-memory");
return 0;
}
It works fine if you make the comparison operators free functions. In general, relational operators should be free functions so they can convert implicitly on both sides:
template <typename T>
bool operator==(Allocator<T> const &, Allocator<T> const &) { return true; }
template <typename T>
bool operator!=(Allocator<T> const &, Allocator<T> const &) { return false; }
Related
My program has several type of small objects to be created and destroyed very frequently in each thread using make_shared, and the shared_ptr will not be passed to another thread, in which case, I decide to write a custom allocate_shared allocator with a boost::pool as its member to allocate fixed size of memory according to the type.
My code is as follows:
ObjectAllocator.h:
#include <boost/pool/pool.hpp>
template<typename T>
class ObjectAllocator
{
public:
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
auto static constexpr block_size=64+sizeof(value_type);
public:
ObjectAllocator() noexcept:pool_(block_size){}
ObjectAllocator(const ObjectAllocator &other) noexcept :pool_(block_size){}
~ObjectAllocator()=default;
template<typename U>
ObjectAllocator(const ObjectAllocator<U> &other) noexcept :pool_(block_size){}
template<typename U>
ObjectAllocator& operator= (const ObjectAllocator<U> &other){
return *this;
}
ObjectAllocator<T>& operator = (const ObjectAllocator &other){
return *this;
}
template<typename U>
struct rebind{ typedef ObjectAllocator<U> other; };
T *allocate(size_type n, const void *hint=nullptr){
#ifdef _DEBUG
assert(n==1);
#endif
return static_cast<T*>(pool_.malloc());
}
void deallocate(T *ptr, size_type n){
#ifdef _DEBUG
assert(n==1);
#endif
pool_.free(ptr);
}
private:
boost::pool<> ObjectAllocator<T>::pool_(block_size);
}
template<typename T, typename U>
inline bool operator == (const ObjectAllocator<T>&, const ObjectAllocator<U>&){
return true;
}
template<typename T, typename U>
inline bool operator != (const ObjectAllocator<T>& a, const ObjectAllocator<U> &b){
return !(a==b);
}
namespace Allocator {
template <typename T>
thread_local ObjectAllocator<T> allocator;
}
main.cpp:
class ObjectA{
public:
int s=0;
void func(){
std::cout<<s<<std::endl;
}
ObjectA() {//std::cout<<"()"<<std::endl;}
~ObjectA() {//std::cout<<"~"<<std::endl;}
};
std::vector<std::shared_ptr<ObjectA>> vec;
void test(){
static uint32_t loop_count=1000*1000;
for(uint32_t i=0;i<loop_count;i++){
shared_ptr<ObjectA> packet = allocate_shared<ObjectA, ObjectAllocator<ObjectA>>(Allocator::allocator<ObjectA>);
vec.push_back(packet);
}
vec.clear();
}
std::vector<std::shared_ptr<ObjectA>> vec2;
void test2(){
static uint32_t loop_count=1000*1000;
for(uint32_t i=0;i<loop_count;i++){
shared_ptr<ObjectA> packet = allocate_shared<ObjectA, ObjectAllocator<ObjectA>>(Allocator::allocator<ObjectA>);
vec2.push_back(packet);
}
vec2.clear();
}
int main() {
std::thread thread1(test);
test2();
return 0;
}
When I try to test it, it crashs and I have no idea why.
Could anyone helps to make it correct? Thanks in advance.
The debugger says seg fault in shared_ptr_base.h
void* _M_get_deleter(const std::type_info& __ti) const noexcept { return _M_pi ? _M_pi->_M_get_deleter(__ti) : nullptr; }
When I try to make boost::pool static, it works fine in single thread and crashes in multi-thread
The debugger says seg fault in shared_ptr_base.h
: _M_use_count(1), _M_weak_count(1) { }
update:
I make boost::pool to be static thread_local and it works properly now
template<typename T>
class ObjectAllocator
{
public:
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
auto static constexpr block_size=64+sizeof(value_type);
public:
ObjectAllocator() noexcept{}
ObjectAllocator(const ObjectAllocator &other) noexcept {}
~ObjectAllocator()=default;
template<typename U>
ObjectAllocator(const ObjectAllocator<U> &other) noexcept {}
template<typename U>
ObjectAllocator& operator= (const ObjectAllocator<U> &other){
return *this;
}
ObjectAllocator<T>& operator = (const ObjectAllocator &other){
return *this;
}
template<typename U>
struct rebind{ typedef ObjectAllocator<U> other; };
T *allocate(size_type n, const void *hint=nullptr){
#ifdef _DEBUG
assert(n==1);
#endif
return static_cast<T*>(pool_.malloc());
}
void deallocate(T *ptr, size_type n){
#ifdef _DEBUG
assert(n==1);
#endif
pool_.free(ptr);
}
private:
thread_local static boost::pool<> pool_;
};
template<typename T>
thread_local boost::pool<> ObjectAllocator<T>::pool_(block_size);
template<typename T, typename U>
inline bool operator == (const ObjectAllocator<T>&, const ObjectAllocator<U>&){
return true;
}
template<typename T, typename U>
inline bool operator != (const ObjectAllocator<T>& a, const ObjectAllocator<U> &b){
return !(a==b);
}
namespace Allocator {
template <typename T>
thread_local static ObjectAllocator<T> allocator;
}
template <typename T, typename ...Args>
inline auto custom_make_shared(Args... args){
return std::allocate_shared<T,ObjectAllocator<T>>(Allocator::allocator<T>,std::forward<Args>(args)...);
}
Both your copy constructors for ObjectAllocator create a new instance of boost::pool each time they're called.
As std::allocate_shared copies the allocator (cppreference), the instance of ObjectAllocator used to allocate std::shared_ptr gets destructed with it's pool before the shared_ptr is destroyed.
Related question: C++ stateful allocator de-allocate issues
Probably unrelated to you problem, but there are also few other issues:
you don't join thread1 in main. This will call std::terminate and crash you program.
boost::pool<> ObjectAllocator<T>::pool_(block_size); - the ObjectAllocator<T>:: part is superfluous and nonstandard. (afaik accepted only in MSVC)
So I'm currently in the process of writing a memory debugger and to do that I need stl container objects to use an untracked allocator.
I have std::string peppered throughout my entire codebase, so I typedef'd it to use my untracked allocator:
typedef std::basic_string<char, std::char_traits<char>, UntrackedAllocator<char>> String;
Now when I try to do this:
String str { "Some string" };
String copy = str;
I get this error:
/usr/local/include/c++/7.1.0/ext/alloc_traits.h:95:67: error: no matching function for call to 'UntrackedAllocator<char>::UntrackedAllocator(UntrackedAllocator<char>)' { return _Base_type::select_on_container_copy_construction(__a); }
This is what my Untracked Allocator looks like:
#pragma once
#define NOMINMAX
#undef max
template <typename T>
class UntrackedAllocator {
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
public:
template<typename U>
struct rebind {
typedef UntrackedAllocator<U> other;
};
public:
inline explicit UntrackedAllocator() {}
inline ~UntrackedAllocator() {}
inline explicit UntrackedAllocator(UntrackedAllocator const&) {}
template<typename U>
inline explicit UntrackedAllocator(UntrackedAllocator<U> const&) {}
// address
inline pointer address(reference r) {
return &r;
}
inline const_pointer address(const_reference r) {
return &r;
}
// memory allocation
inline pointer allocate(size_type cnt,
typename std::allocator<void>::const_pointer = 0) {
T *ptr = (T*)malloc(cnt * sizeof(T));
return ptr;
}
inline void deallocate(pointer p, size_type cnt) {
free(p);
}
// size
inline size_type max_size() const {
return std::numeric_limits<size_type>::max() / sizeof(T);
}
// construction/destruction
inline void construct(pointer p, const T& t) {
new(p) T(t);
}
inline void destroy(pointer p) {
p->~T();
}
inline bool operator==(UntrackedAllocator const& a) { return this == &a; }
inline bool operator!=(UntrackedAllocator const& a) { return !operator==(a); }
};
This is my first time working with custom allocators so I have no idea what's going on with it. It's incredibly annoyning that I can't do str1 = str2 if one of them uses a custom allocator.
The problem is the declaration of the copy c'tors as explicit.
Changing the UntrackedAllocator copy c'tor to:
inline UntrackedAllocator(UntrackedAllocator const&) {}
Solves the compilation issue and everything works just fine:
int main() {
String str { "13" };
String copy = str;
const char* cstr = str.c_str();
int out = atoi(cstr);
}
This happens because the assignment operator of the std::basic_string that accepts const std::basic_string & requires an implicit copy construction of the allocator.
This question already has answers here:
Why can templates only be implemented in the header file?
(17 answers)
Closed 7 years ago.
The code was placed in three files: test.hpp, test.cpp, another.cpp.
source code test.hpp:
#ifndef TEST_HPP_
#define TEST_HPP_
template<typename T> class Allocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
template<typename O> struct rebind { typedef Allocator<O> other; };
Allocator() {}
Allocator(const Allocator& alloc) {}
template<typename O> Allocator(const Allocator<O>& alloc) {}
~Allocator() {}
pointer address(reference __x) const { return &__x; }
const_pointer address(const_reference __x) const { return &__x; }
void construct(pointer p, const T& value) { new ((void *)p) T(value); }
void destroy(pointer p) { p->~T(); }
pointer allocate(size_type n, const_pointer pHint = nullptr);// { return nullptr; }
void deallocate(pointer p, size_type size = 0) {}
inline size_type max_size() const { return 0; }
};
template<typename T> inline bool operator==(const Allocator<T>&, const Allocator<T>&) { return true; }
template<typename T> inline bool operator!=(const Allocator<T>&, const Allocator<T>&) { return false; }
class String : public std::basic_string<wchar_t, std::char_traits<wchar_t>, Allocator<wchar_t>>
{
typedef std::basic_string<wchar_t, std::char_traits<wchar_t>, Allocator<wchar_t>> string_type;
public:
~String() {}
String(const wchar_t* value = L"") : string_type(value, Allocator<wchar_t>()) {}
};
#endif /* TEST_HPP_ */
source code test.cpp:
#include <iostream>
#include <string>
#include <ctime>
#include <type_traits>
#include <functional>
#include <cstddef>
#include "test.hpp"
#define MILLIS(time) (clock() - time) * 1000 / CLOCKS_PER_SEC
int main()
{
String string = L"OK";
return 0;
}
source code another.cpp:
#include <iostream>
#include <string>
#include <ctime>
#include <type_traits>
#include <functional>
#include <cstddef>
#include "test.hpp"
template<typename T> T* Allocator<T>::allocate(size_t n, const T* pHint)
{
return nullptr;
}
Compiled them I got linkage error:
test.cpp:(.text.startup+0x95): undefined reference to `Allocator::allocate(unsigned long, char const*)'
However, if move the code body in another.cpp into test.hpp, like this:
pointer allocate(size_type n, const_pointer pHint = nullptr) { return nullptr; }
Compilation will be successful. Since my custom Allocator::allocate() is somewhat complex, it is inconvenient that placing the code body into test.hpp header file.
Does anybody give me some suggestions? thanks.
The compiler cannot know where your implementation is. Since the test.hpp defines the methods, the compiler expects the implementation in test.cpp. This would also be the clean way to structure it in my opinion. However, if absolutely needed, you might get around this by individually compiling and cleverly linking the obj files in the right order. But I would consider that rather dirty.
Having managed to define a custom allocator, it compiles when Char=char while it doesn΄t when Char=char16_t or char32_t . The allocator allocates all Strings sequentially inside the array m ( may be seen debugging and looking at memory ).
#include <string>
#include <limits>
typedef char char8_t; // just for symmetry.
#define charSz 8 // may by 16 or 32. change by hand !
//#define CONCAT_(a,b,c) a ## b ## c
//#define CONCAT(a,b,c) CONCAT_(a,b,c)
//typedef CONCAT(char,charSz,_t) Char; // may be char8_t or char16_t or char32_t
#if charSz == 8
#define STR(s) #s
typedef char8_t Char;
#elif charSz == 16
#define STR(s) u ## #s
typedef char16_t Char;
#elif charSz == 32
#define STR(s) U ## #s
typedef char32_t Char;
#endif
typedef int32_t Token;
typedef unsigned char byte;
typedef size_t addr;addr freePos=0;Token freeT=0;
const size_t heapSize=0x400;byte m[heapSize];
addr Allocate(size_t sz){addr t=freePos;freePos+=sz;return t;}
void Deallocate(addr p,size_t sz){/**((size_t*)(m+p))=sz;*/}
using namespace std;
template <typename T>
struct Allocator {
// http://www.codeproject.com/Articles/4795/C-Standard-Allocator-An-Introduction-and-Implement
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef value_type& reference;typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template<typename U> struct rebind {typedef Allocator<U> other;};
inline Allocator() {}
// The initialization of freePos and freeT is done after the class definition.
// Doing it in the class creator ins't correct because another intializer of another Allocator
// class would reinitialize that.
inline ~Allocator() {}
inline Allocator(Allocator const&) {}
inline addr address(reference r) {return (byte*)(&r)-m;}
inline addr address(const_reference r) {return (byte*)(&r)-m;}
//static inline offset_type allocate(size_type n, typename std::allocator<void>::const_pointer hint = 0)
// offset_type t=freePos;freePos+=n*sizeof(T);return t;
static inline pointer allocate(size_type n){return (pointer)(m+Allocate(n*sizeof(T)));}
static void deallocate(pointer p, size_type n){Deallocate((byte*)p-m,sizeof(T)*n);}
inline size_type max_size() const{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
inline void construct(pointer p, const T& t) {}
inline void destroy(pointer p) {}
};
template <typename T>
bool operator==(Allocator<T> const &, Allocator<T> const &) { return true; }
template <typename T>
bool operator!=(Allocator<T> const &, Allocator<T> const &) { return false; }
typedef std::basic_string< Char,std::char_traits<Char>,Allocator<Char> > String;
int main(){
for (size_t i=0;i<sizeof(m);i++) m[i]=0xDD; // fill memory to be able to see changes - especially 0s
String s=STR(nice) ;
String t=STR(very nice) ;
String u=STR(good) ;
return 0;
}
Adding
template<typename U> Allocator(U){}
works !
I am defining a custom allocator but I need to keep pointers of the allocator as offsets. As I change the pointer definition to a "size_t" type the code doesn't compile again with no apparent clues !
The need is to create a custom allocator where the memory is addressed in a "segment:offset" style and the address of the segment ( object ) is relocatable.
#include <bits/c++config.h>
#define _GLIBCXX_FULLY_DYNAMIC_STRING 0
#include <stdint.h>
#include <stddef.h>
#include <memory>
#include <string>
#include <limits>
typedef int32_t Token;
typedef unsigned char byte;
using namespace std;
template <typename T>
struct Allocator {
public:
// http://www.codeproject.com/Articles/4795/C-Standard-Allocator-An-Introduction-and-Implement
typedef T value_type;
typedef std::size_t pointer;
typedef const std::size_t const_pointer;
typedef value_type& reference;typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template<typename U> struct rebind {typedef Allocator<U> other;};
static const size_t heapSize=0x1000;
static pointer freePos;
static Token freeT;
static byte m[heapSize];
inline explicit Allocator() {freeT=0;freePos=0;}
inline ~Allocator() {}
inline Allocator(Allocator const&) {} // with explicit it doesn't compile
inline pointer address(reference r) {return &r;}
inline const_pointer address(const_reference r) {return &r;}
//static inline pointer allocate(size_type n, typename std::allocator<void>::const_pointer hint = 0){
// pointer t=freePos;freePos+=n*sizeof(T);return t;
static inline pointer allocate(size_type n){pointer t=freePos;freePos+=n*sizeof(T);return t;}
static void deallocate(pointer p, size_type n){
*(pointer*)((byte*)m+p)=(pointer)(n*sizeof(T));
}
static inline void deallocate(T* p,size_type n){deallocate((pointer)((byte*)p-m),n);}
static inline void deallocate(const T* p,size_type n){deallocate((pointer)((byte*)p-m),n);}
inline size_type max_size() const{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
inline void construct(pointer p, const T& t) {}
inline void destroy(pointer p) {}
};
template <typename T>
bool operator==(Allocator<T> const &, Allocator<T> const &) { return true; }
template <typename T>
bool operator!=(Allocator<T> const &, Allocator<T> const &) { return false; }
using namespace std;
typedef std::basic_string< char,std::char_traits<char>,Allocator<char> > String;
int main(){
String s("nice");
String t("very nice");
String u("good");
return 0;
}
Keep the definitions of pointer and const_pointer the way the standard library expects it to be:
typedef T* pointer;
typedef const T* const_pointer;
Add a new typedef:
typedef std::size_t offset_type;
Use offset_type instead of pointer in suitable places.
Here's a version that compiles and builds but produces Segmentation Fault at run time. I'll leave it to you to figure out that problem.
#include <bits/c++config.h>
#define _GLIBCXX_FULLY_DYNAMIC_STRING 0
#include <stdint.h>
#include <stddef.h>
#include <memory>
#include <string>
#include <limits>
typedef int32_t Token;
typedef unsigned char byte;
using namespace std;
template <typename T>
struct Allocator {
public:
// http://www.codeproject.com/Articles/4795/C-Standard-Allocator-An-Introduction-and-Implement
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef std::size_t offset_type;
typedef value_type& reference;typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template<typename U> struct rebind {typedef Allocator<U> other;};
static const size_t heapSize=0x1000;
static offset_type freePos;
static Token freeT;
static byte m[heapSize];
inline explicit Allocator() {freeT=0;freePos=0;}
inline ~Allocator() {}
inline Allocator(Allocator const&) {} // with explicit it doesn't compile
inline offset_type address(reference r) {return &r;}
inline const_pointer address(const_reference r) {return &r;}
//static inline offset_type allocate(size_type n, typename std::allocator<void>::const_pointer hint = 0)
// offset_type t=freePos;freePos+=n*sizeof(T);return t;
static inline pointer allocate(size_type n){offset_type t=freePos;freePos+=n*sizeof(T);return (pointer)t;}
static void deallocate(offset_type p, size_type n){
*(offset_type*)((byte*)m+p)=(offset_type)(n*sizeof(T));
}
static inline void deallocate(pointer p,size_type n){deallocate((offset_type)((byte*)p-m),n);}
static inline void deallocate(const_pointer* p,size_type n){deallocate((offset_type)((byte*)p-m),n);}
inline size_type max_size() const{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
inline void construct(offset_type p, const T& t) {}
inline void destroy(offset_type p) {}
};
template <typename T>
typename Allocator<T>::offset_type Allocator<T>::freePos;
template <typename T>
Token Allocator<T>::freeT;
template <typename T>
byte Allocator<T>::m[Allocator::heapSize];
template <typename T>
bool operator==(Allocator<T> const &, Allocator<T> const &) { return true; }
template <typename T>
bool operator!=(Allocator<T> const &, Allocator<T> const &) { return false; }
using namespace std;
typedef std::basic_string< char,std::char_traits<char>,Allocator<char> > String;
int main(){
String s("nice");
String t("very nice");
String u("good");
return 0;
}