1

As a mini project, I've been working on creating a Ptr class to duplicate the mechanism of a pointer - the user should theoretically be able to use this class just like a real pointer without issue and without needing to interact with "actual" C++ pointers (besides some obvious syntax changes). The "memory store" is a very large array of bytes. However, I've been getting stuck on assigning dereferenced pointers (ex. *p1 = 123;).

Essentially, the dereference operator has the signature of T& operator*(), where it returns the T item by reference. The issue is that say someone tries to write something like *p1 = -12367890;. This means that within my global memory array, at whatever index p1 is pointing to, I need to overwrite those contents with -12367890 so the memory reflects the assignment made. However, I'm not sure how to override this assignment operator to T such that I can modify memory. Could anyone give me some insight as to how I can somehow overload the dereferenced assignment operator so that I can make modifications to my Memory class?

If it helps, my classes look something like the following (stripped down to their bare bones of course):

// constants.h
const int CAPACITY = 4000;

// memory.h
class Memory
{
public:
    Memory();
    ....
private:
    byte _memory[CAPACITY];
    ....
};

// ptr.h
template<typename T>
class Ptr
{
    Ptr<T>(){}
    T& operator*();        // ISSUE
    ....
private:
    int _index;
};

// serialize.h (global functions for serialization)
template<typename T>
array<byte, sizeof(T)> serialize(const T& object);

template<typename T>
T& deserialize(const array<byte, sizeof(T)>& bytes, T& object);

The driver code for the program would look like this:

Ptr<int> p1;          // initialize pointer
p1._new();            // allocate space in global memory array
 *p1 = -12367890;     // assign dereferenced pointer
thean
  • 97
  • 9
  • 2
    The only way to keep that signature is to return a reference to the `T` you want to modify. Your code is missing too much details to answer exactly how to do that in your case, but with some guessing you basically find the memory in your array that holds the object, cast it to a `T&` and return that. – super Apr 14 '21 at 05:31
  • Actually this was perfect advice, thank you! Figured it out by doing pretty much exactly what you said - casting the byte pointer to T and returning that – thean Apr 14 '21 at 05:45

1 Answers1

0

Figured it out, thanks to @super.

My working method looks like this now:

T& operator*()
{
   return *(T*)_memory.get_index(_index);
}

where _memory.get_index(_index) returns a byte pointer at index _index of the global memory array.

thean
  • 97
  • 9
  • 1
    This violates the strict aliasing rule and invokes undefined behavior. – Patrick Roberts Apr 14 '21 at 05:54
  • @PatrickRoberts That's interesting, this is exactly how I would have done it. How could this be done without invoking undefined behavior? – asynts Apr 14 '21 at 07:04
  • 1
    @asynts [Is there a (semantic) difference between the return value of placement new and the casted value of its operand?](https://stackoverflow.com/q/47653305/1541563) Additionally, since `_memory.get_index(_index)` won't necessarily be `alignas(alignof(T))`, [on certain platforms this unaligned access could easily lead to a segfault](https://stackoverflow.com/q/1496848/1541563). This all assumes that `p1._new();` performs a [placement new](https://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new) on the memory, which is also an additional requirement. – Patrick Roberts Apr 14 '21 at 07:22
  • */me has to go through my codebase and replace `reinterpret_cast` with placement new.* Thanks! – asynts Apr 14 '21 at 07:30
  • 1
    @asynts That's... not the correct take-away from those links. `reinterpret_cast` and placement new accomplish completely different things, and both have their correct usage. One should not be blindly replaced with another. – Patrick Roberts Apr 14 '21 at 07:39
  • 1
    @PatrickRoberts Well currently, I just use `reinterpret_cast` for situations where I should be using placement new (I'll do more research before changing stuff.) For stuff like type-erasure I'll keep `reinterpret_cast`, although, I'll have to check if I need something like `std::launder`. Man, C++ is complicated! – asynts Apr 14 '21 at 07:42