Making a safe buffer holder in C++ - c++

There are situations in which I need to pass a char* buffer back and forth. My idea is to create an object which can hold the object that owns the data, but also expose the data as char* for someone to read. Since this object holds the owner, there are no memory leaks because the owner is destructed with the object when it's no longer necessary.
I came with the implementation below, in which we have a segfault that I explain why it happens. In fact it's something that I know how to fix but it's something that my class kinda lured me into doing. So I consider what I've done to be not good and maybe there's a better way of doing this in C++ that is safer.
Please take a look at my class that holds the buffer owner and also holds the raw pointer to that buffer. I used GenericObjectHolder to be something that holds the owner for me, without my Buffer class being parametrized by this owner.
#include <iostream>
#include <string>
#include <memory>
#include <queue>
//The library:
class GenericObjectHolder
{
public:
GenericObjectHolder()
{
}
virtual ~GenericObjectHolder() {
};
};
template <class T, class Holder = GenericObjectHolder>
class Buffer final
{
public:
//Ownership WILL be passed to this object
static Buffer fromOwned(T rawBuffer, size_t size)
{
return Buffer(std::make_unique<T>(rawBuffer), size);
}
//Creates a buffer from an object that holds the buffer
//ownership and saves the object too so it's only destructed
//when this buffer itself is destructed
static Buffer fromObject(T rawBuffer, size_t size, Holder *holder)
{
return Buffer(rawBuffer, std::make_unique<T>(rawBuffer), size, holder);
}
//Allocates a new buffer with a size
static Buffer allocate(size_t size)
{
return Buffer(std::make_unique<T>(new T[size]), size);
}
~Buffer()
{
if (_holder)
delete _holder;
}
virtual T data()
{
return _rawBuffer;
}
virtual size_t size() const
{
return _size;
}
Buffer(T rawBuffer, std::unique_ptr<T> buffer, size_t size)
{
_rawBuffer = rawBuffer;
_buffer = std::move(buffer);
_size = size;
}
Buffer(T rawBuffer, std::unique_ptr<T> buffer, size_t size, Holder *holder)
{
_rawBuffer = rawBuffer;
_buffer = std::move(buffer);
_size = size;
_holder = holder;
}
Buffer(const Buffer &other)
: _size(other._size),
_holder(other._holder),
_buffer(std::make_unique<T>(*other._buffer))
{
}
private:
Holder *_holder;
T _rawBuffer;
std::unique_ptr<T> _buffer;
size_t _size = 0;
};
//Usage:
template <class T>
class MyHolder : public GenericObjectHolder
{
public:
MyHolder(T t) : t(t)
{
}
~MyHolder()
{
}
private:
T t;
};
int main()
{
std::queue<Buffer<const char*, MyHolder<std::string>>> queue;
std::cout << "begin" << std::endl;
{
//This string is going to be deleted, but `MyHolder` will still hold
//its buffer
std::string s("hello");
auto h = new MyHolder<std::string>(s);
auto b = Buffer<const char*, MyHolder<std::string>>::fromObject(s.c_str(),s.size(), h);
queue.emplace(b);
}
{
auto b = queue.front();
//We try to print the buffer from a deleted string, segfault
printf(b.data());
printf("\n");
}
std::cout << "end" << std::endl;
}
As you see, the s string is copied inside the object holder but gets destructed right after it. So when I try to access the raw buffer that buffer owns I get a segfault.
Of course I could simply copy the buffer from the s string into a new buffer inside my object, but It'd be inefficient.
Maybe there's a better way of doing such thing or maybe there's even something ready in C++ that does what I need.
PS: string is just an example. In pratice I could be dealing with any type of object that owns a char* buffer.
Live example: https://repl.it/repls/IncredibleHomelySdk

Your core problem is that you want your Holder to be moveable. But when the Owner object moves, the buffer object might also move. That will invalidate your pointer. You can avoid that by putting the owner in a fixed heap location via unique_ptr:
#include <string>
#include <memory>
#include <queue>
#include <functional>
template <class B, class Owner>
class Buffer
{
public:
Buffer(std::unique_ptr<Owner>&& owner, B buf, size_t size) :
_owner(std::move(owner)), _buf(std::move(buf)), _size(size)
{}
B data() { return _buf; }
size_t size() { return _size; }
private:
std::unique_ptr<Owner> _owner;
B _buf;
size_t _size;
};
//Allocates a new buffer with a size
template<typename T>
Buffer<T*, T[]> alloc_buffer(size_t size) {
auto buf = std::make_unique<T[]>(size);
return {std::move(buf), buf.get(), size};
}
Here's a repl link: https://repl.it/repls/TemporalFreshApi
If you want to have a type-erased Buffer, you can do that like this:
template <class B>
class Buffer
{
public:
virtual ~Buffer() = default;
B* data() { return _buf; }
size_t size() { return _size; }
protected:
Buffer(B* buf, size_t size) :
_buf(buf), _size(size) {};
B* _buf;
size_t _size;
};
template <class B, class Owner>
class BufferImpl : public Buffer<B>
{
public:
BufferImpl(std::unique_ptr<Owner>&& owner, B* buf, size_t size) :
Buffer<B>(buf, size), _owner(std::move(owner))
{}
private:
std::unique_ptr<Owner> _owner;
};
//Allocates a new buffer with a size
template<typename T>
std::unique_ptr<Buffer<T>> alloc_buffer(size_t size) {
auto buf = std::make_unique<T[]>(size);
return std::make_unique<BufferImpl<T, T[]>>(std::move(buf), buf.get(), size);
}
Again, repl link: https://repl.it/repls/YouthfulBoringSoftware#main.cpp

You wrote:
There are situations in which I need to pass a char* buffer back and
forth.
and
So I consider what I've done to be not good and maybe there's a better
way of doing this in C++ that is safer.
It's not exactly clear what you are aiming at, but when I have this need i will sometimes use std::vector<char> - a std::vector (and std::string) is a just that: a managed buffer. Calling data() on vector will give you a raw pointer to the buffer to pass on to legacy interfaces etc. or for whatever reason you just need a buffer that you manage yourself. Hint: use resize() or constructor to allocate the buffer.
So you see, there's no need to store the internal pointer of std::string in your example. Instead just call data() on a need basis.
It seems like you are concerned about copies and efficiency. If you use objects that support move semantics and you use the emplace family of functions there shouldn't be any copy-ing going on at least in c++17. All/most containers supports moving as well.

The class std::unique_ptr is already a "buffer holder" that "guarantee delete", no string copies, no dangling references and no seg faults:
#include <iostream>
#include <queue>
#include <memory>
int main()
{
std::queue<std::unique_ptr<std::string>> queue;
std::cout << "begin" << std::endl;
{
auto h = std::make_unique<std::string>("Hello");
queue.emplace( std::move(h) ); // move into the queue without copy
}
{
auto b = std::move(queue.front()); // move out from queue without copy
std::cout << *b << std::endl;
} // when b goes out of scope it delete the string
std::cout << "end" << std::endl;
}
https://godbolt.org/z/neP838

Related

Access violation when reserve is called on a map

I have a few unordered_maps that I use with custom allocators. In this case I use a rudimentary bump allocator that just simply linearly allocates new memory from an existing continiuos block.
But when I try and call reserve on these maps after a while, they throw an access violaton exception at the line within the std list file I'll show below in :
_List_unchecked_const_iterator& operator++() noexcept {
_Ptr = _Ptr->_Next;
return *this;
}
My allocator has enough memory left so I don't think it's because I am running out of memory.
Here's some self contained code that demonstrates it. Copy/paste and run it to see the issue.
#include <stdlib.h>
#include <unordered_map>
#include <vector>
#include <list>
#include <memory>
#define SEQ(type, val) sizeof(type) * val
struct ImpAllocator {
virtual void* Allocate(size_t pSize) = 0;
virtual void Free(void* pPtr) = 0;
};
struct SysAllocator : public ImpAllocator {
void* Allocate(size_t pSize) override {
return malloc(pSize);
}
void Free(void* pPtr) override {
free(pPtr);
}
};
template <class T>
class StdAllocatorWrapper {
public:
std::shared_ptr<ImpAllocator> mInternalAllocator;
using value_type = T;
StdAllocatorWrapper() = default;
StdAllocatorWrapper(std::shared_ptr<ImpAllocator> pInternalAllocator) :
mInternalAllocator(pInternalAllocator)
{}
~StdAllocatorWrapper() = default;
StdAllocatorWrapper(const StdAllocatorWrapper<T>& pOther) = default;
template<class U>
StdAllocatorWrapper(const StdAllocatorWrapper<U>& pOther) {
this->mInternalAllocator = pOther.mInternalAllocator;
}
value_type* allocate(size_t pNumberOfObjects) {
return reinterpret_cast<T*>(mInternalAllocator->Allocate(SEQ(T, pNumberOfObjects)));
}
void deallocate(value_type* pPointer, size_t pNumberOfObjects) {
mInternalAllocator->Free(pPointer);
}
};
template <class T, class U>
bool operator==(StdAllocatorWrapper<T> const& pL, StdAllocatorWrapper<U> const& pR) noexcept {
return pL.mInternalAllocator == pR.mInternalAllocator;
}
template <class T, class U>
bool operator!=(StdAllocatorWrapper<T> const& pL, StdAllocatorWrapper<U> const& pR) noexcept {
return !(pL == pR);
}
template<typename T> using AllocWrapper = StdAllocatorWrapper<T>;
template<typename T, typename K> using Pair = std::pair<const T, K>;
template<typename T, typename K> using PairAllocWrapper = StdAllocatorWrapper<Pair<T, K>>;
template<typename T> using AllocatedVector = std::vector<T, AllocWrapper<T>>;
template<typename T> using AllocatedList = std::list<T, AllocWrapper<T>>;
template<typename T, typename K> using AllocatedUnorderedMap = std::unordered_map<T, K, std::hash<T>, std::equal_to<T>, PairAllocWrapper<T, K>>;
typedef unsigned char* MemBlock;
class BumpAllocator : public ImpAllocator {
private:
std::shared_ptr<ImpAllocator> mInternalAllocator;
size_t mSize;
MemBlock mBlock;
MemBlock mStart;;
size_t mCurrent;
public:
BumpAllocator(size_t pSize, std::shared_ptr<ImpAllocator> pInternalAllocator) :
mInternalAllocator(pInternalAllocator),
mSize(pSize),
mCurrent(0) {
mBlock = reinterpret_cast<MemBlock>(mInternalAllocator->Allocate(pSize));
mStart = mBlock;
}
~BumpAllocator() {
mInternalAllocator->Free(mBlock);
}
void* Allocate(size_t pSize) override {
printf("\n bump allocator wrapper requested: %d", pSize);
if (mCurrent + pSize > mSize) {
return nullptr;
}
MemBlock _return = mBlock + mCurrent;
mCurrent += pSize;
return _return;
}
void Free(void* pFre) override {
}
void Reset() {
mCurrent = 0;
}
};
struct Animation {
};
struct Texture {
};
struct TextureArrayIndex {
//TexturePointer mTexture;
unsigned int mIndex;
std::shared_ptr<Texture> mTexture;
};
struct RenderOrder {
float mDeltaTime;
std::string mAnimationName;
std::shared_ptr<Animation> mAnim;
};
using Textures = AllocatedUnorderedMap<int, TextureArrayIndex>;
using TexturesAllocWrapper = PairAllocWrapper<int, TextureArrayIndex>;
using RenderOrdersVector = AllocatedVector<RenderOrder>;
using RenderOrdersAllocWrapper = AllocWrapper<RenderOrder>;
using RenderBucket = AllocatedUnorderedMap<unsigned int, RenderOrdersVector>;
using RenderBuckets = AllocatedUnorderedMap<std::shared_ptr<Animation>, RenderBucket>;
using RenderBucketAllocWrapper = PairAllocWrapper<unsigned int, RenderOrdersVector>;
using RenderBucketsAllocWrapper = PairAllocWrapper<std::shared_ptr<Animation>, RenderBucket>;
struct Renderer {
std::shared_ptr<BumpAllocator> mInternalAllocator;
Textures mTextureArrayIndexMap;
RenderBuckets mAnimationRenderBuckets;
Renderer(std::shared_ptr<ImpAllocator> pAllocator) :
mInternalAllocator(std::make_shared<BumpAllocator>(60000, pAllocator)),
mTextureArrayIndexMap(Textures(TexturesAllocWrapper(mInternalAllocator))),
mAnimationRenderBuckets(RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator)))
{}
void Begin() {
mTextureArrayIndexMap = Textures(TexturesAllocWrapper(mInternalAllocator));
mTextureArrayIndexMap.reserve(2);
mAnimationRenderBuckets = RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator));
mAnimationRenderBuckets.reserve(1000);
}
void Render() {
}
void Flush() {
mInternalAllocator->Reset();
}
};
int main(int argc, char* argv[]) {
Renderer _renderer(std::make_shared<SysAllocator>());
for (int i = 0; i < 1000; i++) {
_renderer.Begin();
_renderer.Flush();
}
}
What's weird is that , the first 2 iterations work fine. But the third one fails... So reusing the memory works for the first 2 Begin--->Flush cycles but then the 3d one fail every time. So odd.
I could not reproduce the error with the code given... But the code does not use std::unordered_map, so the error must come from there.
First issue. In your allocators:
void* SysAllocator::Allocate(size_t pSize) override {
return malloc(pSize); // allocator returns NULL on error.
}
void* BumpAllocator::Allocate(size_t pSize) override {
printf("\n bump allocator wrapper requested: %d", pSize);
if (mCurrent + pSize > mSize) {
return nullptr; // allocator returns NULL on error.
}
MemBlock _return = mBlock + mCurrent;
mCurrent += pSize;
return _return;
}
Your custom allocators return NULL on error, but STL containers expect their allocators to throw a std::bad_alloc exception on error. That's mandatory.
You should change your allocators:
void* SysAllocator::Allocate(size_t pSize) override {
void* p = malloc(pSize);
if (!p)
throw std::bad_alloc();
return p;
}
void* BumpAllocator::Allocate(size_t pSize) override {
printf("\n bump allocator wrapper requested: %d", pSize);
if (mCurrent + pSize > mSize)
throw std::bad_alloc();
MemBlock _return = mBlock + mCurrent;
mCurrent += pSize;
return _return;
}
This will not solve your problem, but the point of error will have moved to the time of allocation.
I think your problem comes from a lack of memory. std::unordered_map uses hash buckets, and these could very likely use more memory than you anticipated. To investigate, set breakpoints on the throw lines in your Allocate() functions to catch the error right when it happens.
Apart from the issue mentioned by Michael Roy, there is another bug here
void Reset() {
mCurrent = 0;
}
This method, which is called indirectly from your main, means that further allocations will reuse already allocated memory. This is the immediate cause of the crash in the code you posted.
If you comment out the line mCurrent = 0; then you do actually run out of memory and you get the null pointer bug explained by Michael Roy in the other answer.
Tested with MSVC (since that seems to be significant).
I figured it out finally. The problem was that inside the Begin method, I am initializing the maps again. This happens after Reset is called. Meaning the memory allocation takes place from the beginning. This would be fine. However, at this time. Both the new maps and the old ones exist at the same time and they overlap fully on the same memory. This also would be fine but then when the new map is assigned to the old one, the destructor of the old one is called and it frees the memory that is being used by the newly instantiated maps.
I fixed the issue by making the maps pointers ( std::unordered_map* ) . So now my map "reinstantiation" is as follows mTextureArrayIndexMap = new Textures(...) . So the destructor of the old map is not called. And this is perfectly fine since the whole thing is being allocated on a bump memory that is reset.
The code runs smoothly now with no issues!

How to change a map's memory resource

I am having trouble changing the memory resource of a map in my custom container.
It seems I can neither change the allocator of the map, nor create a new map with the custom memory resource.
#include <iostream>
#include <memory_resource>
#include <map>
using Map = std::pmr::map<const std::string, int>;
class custom_resource : public std::pmr::memory_resource {
public:
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
std::cout << "Using custom resource" << std::endl;
return std::pmr::get_default_resource()->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return false;
}
};
struct MyContainer {
void Reset(std::pmr::memory_resource &resource)
{
map_ = Map{&resource};
}
void AddItem(std::string k) {
map_[k] = 1;
}
std::pmr::memory_resource* default_resource = std::pmr::get_default_resource();
// custom_resource custom{}; // uncomment this and next line to see it work
// Map map_{&custom};
Map map_{default_resource};
};
int main() {
MyContainer container{};
container.AddItem("a"); // no output expected
custom_resource custom{};
container.Reset(custom);
container.AddItem("b"); // output expected
return 0;
}
https://godbolt.org/z/aVMuw6
How can I reset the container at runtime such that the custom memory resource is used to allocate items in the map?
You cannot change a container's Allocator after it has been constructed. And even polymorphic_allocator's memory resource cannot be changed after it has been constructed. So what you want isn't really doable; you are expected to know what memory resource your container will use at the time of its construction and that is the allocator+resource that it will use until its destruction.
There are circumstances that would allow you to copy/move/swap a new allocator into a container, but polymorphic_allocator disallows all of them.

Framework applications using interface without heap memory allocation?

I am trying to create a framework for applications to use in my microprocessors. I am using Arduino IDE to compile and deploy the programs.
Since the microprocessors often have low heap memory, I want to only use stack memory if possible.
Minimial example:
The whole example code can be seen here.
I will describe the parts I think is most interesting.
iMinExApplication (interface):
class iMinExApplication
{
public:
virtual void initialize() = 0; // pure virtual
virtual void execute() = 0; // pure virtual
virtual ~iMinExApplication() = default; // Virtual destructor
};
tMinExApplication (extension of interface, only used by the framework):
class tMinExApplication
{
public:
...
tMinExApplication(iMinExApplication* app, const char name[]) : App(app)
{
strcpy(Name, name);
};
...
void execute() { App->execute(); };
private:
iMinExApplication* App;
char Name[32];
};
tMinExCoordinator (master, calling added apps)
class tMinExCoordinator
{
public:
...
void addApp(iMinExApplication* app, const char name[])
{
tMinExApplication* tmpPtr = new tMinExApplication(app, name); // HERE!
Applications[++NumApps] = tmpPtr;
tmpPtr = nullptr;
};
...
void runApps()
{
for (auto& app : Applications) {
// Frequency check
// ...
app->execute();
}
};
private:
tMinExApplication* Applications[];
int NumApps;
};
tMyApp (user defined app, using the inherited interface)
class tMyApp : public iMinExApplication {
...
minExSketch (Arduino IDE sketch)
#include "tMinExClasses.hpp"
#include "tMyApp.hpp"
tMinExCoordinator coordinator{};
tMyApp tmpApp{};
void setup() {
Serial.begin(9600);
coordinator.addApp(&tmpApp, "TEST");
coordinator.initializeApps();
}
void loop() {
coordinator.runApps();
}
The above works. But the apps are allocated in heap memory, since it uses the keyword new ('HERE!' in the tMinExCoordinator class definition, line 57 in tMinExClasses.hpp).
I cannot seem to get it to work without it.
In what other way could I implement this, but only allocating memory in stack memory?
Requirements:
Stack memory allocation
The interface is to be used.
I have though of smart pointers, but am unsure if they use heap memory or not. Also, I wanted the minimal example as clean as possible.
I cannot seem to get it to work without it. In what other way could I implement this, but only allocating memory in stack memory?*
You can pre-allocate a byte array of sufficient size, and then use placement-new to construct objects inside of that array (see std::aligned_storage to help you with that). Polymorphism only requires pointers/references to work at runtime, not dynamic alllocations.
template<std::size_t MaxApps>
class tMinExCoordinator
{
public:
...
tMinExCoordinator()
{
Applications = reinterpret_cast<tMinExApplication*>(appBuffer);
}
~tMinExCoordinator()
{
for (std::size_t i = 0; i < NumApps; ++i)
Applications[i].~tMinExApplication();
}
void addApp(iMinExApplication* app, const char name[])
{
if (NumApps >= MaxApps)
throw std::length_error("");
new (&appBuffer[NumApps]) tMinExApplication(app, name);
++NumApps;
}
...
void runApps()
{
for (std::size_t i = 0; i < NumApps; ++i)
{
auto& app = Applications[i];
// Frequency check
// ...
app.execute();
}
}
private:
typename std::aligned_storage<sizeof(tMinExApplication), alignof(tMinExApplication)>::type appBuffer[MaxApps];
tMinExApplication* Applications;
std::size_t NumApps = 0;
};
tMinExCoordinator<1> coordinator{};
...
The std::aligned_storage documentation linked above has an example static_vector class that uses a fixed memory buffer, which will be on the stack if the vector is constructed on the stack:
#include <iostream>
#include <type_traits>
#include <string>
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an object in aligned storage
const T& operator[](std::size_t pos) const
{
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data[pos]);
}
// Delete objects from aligned storage
~static_vector()
{
for(std::size_t pos = 0; pos < m_size; ++pos) {
// note: needs std::launder as of C++17
reinterpret_cast<T*>(&data[pos])->~T();
}
}
};
You can use that class in your coordinator, with some minor additions to it so it can work with loops, eg:
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an object in aligned storage
T& operator[](std::size_t pos)
{
// note: needs std::launder as of C++17
return *reinterpret_cast<T*>(&data[pos]);
}
const T& operator[](std::size_t pos) const
{
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data[pos]);
}
std::size_t size() const { return m_size; }
std::size_t capacity() const { return N; }
// iterator access to objects
T* begin()
{
// note: needs std::launder as of C++17
return reinterpret_cast<T*>(&data[0]);
}
T* end()
{
// note: needs std::launder as of C++17
return reinterpret_cast<T*>(&data[m_size]);
}
const T* cbegin() const
{
// note: needs std::launder as of C++17
return reinterpret_cast<const T*>(&data[0]);
}
const T* cend() const
{
// note: needs std::launder as of C++17
return reinterpret_cast<const T*>(&data[m_size]);
}
// Delete objects from aligned storage
~static_vector()
{
for(std::size_t pos = 0; pos < m_size; ++pos) {
// note: needs std::launder as of C++17
reinterpret_cast<T*>(&data[pos])->~T();
}
}
};
template<std::size_t MaxApps>
class tMinExCoordinator
{
public:
...
void addApp(iMinExApplication* app, const char name[])
{
Applications.emplace_back(app, name);
}
...
void runApps()
{
for (auto& app : Applications)
{
// Frequency check
// ...
app.execute();
}
}
private:
static_vector<tMinExApplication, MaxApps> Applications;
};
I have though of smart pointers, but am unsure if they use heap memory or not.
By default, they rely on new and delete, and thus dynamic memory. Though, you can supply them with pointers to stack memory, if you also supply them with custom deleters that won't free that memory.

Copying the address of an object into a buffer and retrieving it

I would like to copy the address of an object to a buffer and typecast it back at some other point. I am unable to do it. A sample code is given below.
#include <iostream>
#include <cstring>
class MyClass
{
public:
MyClass(const int & i)
{
id = i;
}
~MyClass()
{
}
void print() const
{
std::cout<<" My id: "<<id<<std::endl;
}
private:
int id;
};
int main()
{
MyClass *myClass = new MyClass(10);
std::cout<<"myClass: "<<myClass<<std::endl;
myClass->print();
// Need to copy the address to a buffer and retrieve it later
char tmp[128];
// std::vector tmp(sizeof(myClass); // preferably, we may use this instead of the previous line, and use std::copy instead of memcpy
memcpy(tmp, myClass, sizeof(myClass));
// retreiving the pointer
MyClass* myClassPtr = (MyClass*) tmp;
std::cout<<"myClassPtr: "<<myClassPtr<<std::endl;
myClassPtr->print();
return 0;
}
In fact, the pointers gives different values, which is the source of the problem. What am I doing wrong here?
You are copying (a pointer-sized part of) the object itself, not the pointer. You should do:
memcpy(tmp, &myClass, sizeof(myClass));
and then back:
MyClass *ptr;
memcpy(&ptr, tmp, sizeof(ptr));

copy constructor with template class not getting called

I was trying to write a sample code for implementing shared pointer [just for practice].
In this following example,
why compiler is not complaining about modifying other_T
And why copy constructor SharedPtr(const T& other_T) is not getting called ?
Here is the code snippet.
#include <iostream>
using namespace std;
#define DBG cout<<"[DEBUG]"<<__PRETTY_FUNCTION__<<endl
class RefCount
{
protected:
int m_ref;
RefCount(){ DBG; m_ref = 1 ; }
void reference(){ DBG; ++m_ref; }
void dereference(){ DBG;--m_ref; }
};
template <class T>
class SharedPtr : public RefCount
{
T* m_T;
public:
SharedPtr() { DBG; m_T = new T; }
SharedPtr(const T& other_T){
DBG;
m_T = other_T.m_T;
other_T.dereference();
other_T.m_T = NULL;
}
~SharedPtr() {
DBG;
dereference();
cout<<m_ref<<endl;
if(m_ref <= 0 && m_T != NULL ){
cout<<"Destroying"<<endl;
delete m_T;
m_T = NULL;
}
}
};
class A{};
int main()
{
SharedPtr<A> obj;
cout<<"assigning "<<endl;
SharedPtr<A> obj2 = obj;
cout<<"END"<<endl;
return 0;
}
and the result is segfault.
Your primary problem is that the copy constructor is being called--but you haven't defined a copy constructor, so you're getting the copy constructor that's defined by the compiler by default.
That copy constructor just does a member-wise copy. That means you've allocated one A with new, then pointed two SharedPtr objects at that same A. The first one to get destroyed deletes the A object. Then the second one gets destroyed, attempts to delete the same object again, and havoc ensues.
In the end, it doesn't look to me like much (any?) of this is going to make any real difference though. I'm pretty sure your basic design is broken. To get a working shared pointer, you have one reference count and "raw" pointer to the final object. Then you have N SharedPtr objects referring to that one ref count/pointer structure that in turn refers to the final object.
You're trying to combine the raw pointer/ref count into the individual SharedPtr, and I don't see any way that can actually work.
It also seems to me that the basic concept of what you've called a RefCount is really part of the design of a SharedPtr. As such, I think its definition should be nested inside that of SharedPtr (and probably made private, since the outside world has no reason to know it exists, not to mention being able to access it directly).
With those taken into account, the code might end up something like this:
#include <iostream>
using namespace std;
#define DBG cout<<"[DEBUG]"<<__PRETTY_FUNCTION__<<endl
template <class T>
class SharedPtr {
template <class U>
struct Ref {
mutable int m_ref;
U *data;
Ref(T *data) : m_ref(1), data(data) { DBG; }
void add_ref() const { DBG; ++m_ref; std::cout << "m_ref=" << m_ref << "\n"; }
void sub_ref() const { DBG; --m_ref; std::cout << "m_ref=" << m_ref << "\n"; }
~Ref() { delete data; }
};
Ref<T> *r;
public:
SharedPtr(T *data) { DBG; r = new Ref<T>(data); }
SharedPtr(SharedPtr const &p) : r(p.r) { DBG; r->add_ref(); }
~SharedPtr() {
DBG;
r->sub_ref();
if (0 == r->m_ref) {
delete r;
std::cout << "deleted pointee\n";
}
}
};
class A{};
int main() {
SharedPtr<A> obj(new A);
cout<<"copying "<<endl;
SharedPtr<A> obj2 = obj;
cout<<"END"<<endl;
return 0;
}
Notes: though this fixes at least some of the basic design, it's still quite a ways short of usable. It's missing the dereference operator, so you can't use the pointer to get to the value it points at. It'll break completely in a multi-threaded environment. I haven't thought enough about it to be sure, but my immediate guess is that it's probably not exception safe either.