2

I have two programs. The first allocates a Shared-Memory file and the second reads from it.. I am using placement-new to place objects into this memory guaranteeing that the objects do NOT use new or allocate any memory outside of the Shared-Memory file.

My Array structure:

template<typename T, size_t Size>
struct SHMArray {
    SHMArray() : ptr(elements) {}
    SHMArray(const SHMArray& other) { std::copy(other.begin(), other.end(), begin()); }

    SHMArray(SHMArray&& other)
    {
        std::swap(other.ptr, ptr);
        std::fill_n(ptr.get(), Size, T());
    }

    ~SHMArray()
    {
        std::fill_n(ptr.get(), Size, T());
    }

    constexpr bool empty() const noexcept
    {
        return Size == 0;
    }

    constexpr size_type size() const noexcept
    {
        return Size;
    }

    T& operator[](std::size_t pos)
    {
        return *(ptr.get() + pos);
    }

    constexpr const T& operator[](std::size_t pos) const
    {
        return *(ptr.get() + pos);
    }

    T* data() noexcept
    {
        return ptr.get();
    }

    constexpr const T* data() const noexcept
    {
        return ptr.get();
    }

private:
    offset_ptr<T> ptr;
    T elements[];
};

Program 1:

int main()
{
    //Allocate a shared memory file of 1mb..
    auto memory_map = SharedMemoryFile("./memory.map", 1024 * 1024, std::ios::in | std::ios::out);

   memory_map.lock();

   //Pointer to the shared memory.
   void* data = memory_map.data();

   //Place the object in the memory..
   SHMArray<int, 3>* array = ::new(data) SHMArray<int, 3>();
   (*array)[0] = 500;
   (*array)[1] = 300;
   (*array)[2] = 200;

   memory_map.unlock(); //signals other program it's okay to read..
}

Program 2:

int main()
{
    //Open the file..
    auto memory_map = SharedMemoryFile("./memory.map", 1024 * 1024, std::ios::in | std::ios::out);

   memory_map.lock();

   //Pointer to the shared memory.
   void* data = memory_map.data();

   //Place the object in the memory..
   //I already understand that I could just cast the `data` to an SHMArray..
   SHMArray<int, 3>* array = ::new(data) SHMArray<int, 3>();
   for (int i = 0; i < array.size(); ++i)
   {
       std::cout<<(*array)[i]<<"\n";
   }

   memory_map.unlock(); //signals other program it's okay to read..
}
  1. Program One placed the SHMArray in memory with placement new. Program Two does the same thing on top of program one's already placed object (overwriting it). Is this undefined behaviour? I don't think it is but I want to confirm.

  2. Neither program calls the destructor array->~SHMVEC(); I also don't think this leaks as long as I close the MemoryMapped file then it should all be fine.. but I want to make sure this is fine. If I ran the programs again on the same file, it shouldn't be a problem.

I am essentially making the assumption that placement new is working as if I placed a C struct in memory in this particular scenario via: struct SHMArray* array = (struct SHMArray*)data;.. Is this correct?

Brandon
  • 22,723
  • 11
  • 93
  • 186
  • 1
    This would be fine if you didn't have that `offset_ptr`. It's useless (because it points to `elements`) except when it's wrong (when it points to `elements` of a moved-from maybe-destroyed instance). – Ben Voigt Sep 16 '19 at 00:33
  • 1
    In the end, though, it would probably be much easier to reason about correctness of an accessor array which is stored *outside* the shared-memory, and defines `operator[]` to access the given pointer into shared memory. An ordinary `T*` would work for that. – Ben Voigt Sep 16 '19 at 00:34

1 Answers1

4

I am essentially making the assumption that placement new is working as if I placed a C struct in memory in this particular scenario via: struct SHMArray* array = (struct SHMArray*)data;.. Is this correct?

No, this is not correct. Placement new also invokes the object's appropriate constructor. "struct SHMArray* array = (struct SHMArray*)data;" does not invoke any object's constructor. It's just a pointer conversion cast. Which does not invoke anyone's constructor. Key difference.

In your sample code, you do actually want to invoke the templated object's constructor. Although the shown example has other issues, as already mentioned in the comments, this does appear to be what needs to be done in this particular situation.

But insofar as the equivalent of placement new versus a pointer cast, no they're not the same. One invokes a constructor, one does not. new always invokes the constructor, whether it's placement new, or not. This is a very important detail, that's not to be overlooked.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • But do I need to call destructor? Would it be wrong if I didn't? Does overwriting the existing object cause any problems? – Brandon Sep 16 '19 at 01:47
  • You need to call the destructor just as much as you need to call the destructor of any other object. If you don't destroy objects, they'll get destroyed when the program terminates. This is no different. – Sam Varshavchik Sep 16 '19 at 01:59
  • @SamVarshavchik: That last comment is highly misleading. You absolutely need to call the destructor for objects created with placement new, but for regular `new` you don't call the destructor. Instead, `delete` calls the destructor for you. – MSalters Sep 16 '19 at 11:19
  • @Brandon: You must call the destructor like `array->~SHMArray()`. – MSalters Sep 16 '19 at 11:21
  • @MSalters; https://stackoverflow.com/a/41385385/1462718 This is what I wanted to know.. if it is 100% necessary and what happens if I overwrite the previous object.. The answer is that it's perfectly safe as long as I'm not doing any allocations in my object I guess. – Brandon Sep 17 '19 at 00:08