Search code examples
c++unique-ptrforward-declaration

How to forward declare custom unique_ptr for shared memory


I've taken the example below from Dr. Rian Quinn's book "Hands-On System Programming with C/C++" modified just a bit. It wraps mmap with a unique_ptr{}. It works almost just like I need. I would like to forward declare the pointer to the mapped memory so I can use it as a private class member. My application is multi-task, each single-threaded, hard real-time with shared memory for cross-task communication. Dr. Quinn has a second example with shared memory that's a little longer than the one shown here but it this one illustrates the problem. The shm_open/mmap is relatively expensive time-wise. I need it to be done once during setup and have no idea how to go about it. I know how to do this with raw pointers. I'm using g++ 4.8.5.

I've tried:

std::unique_ptr<myStruct,mmap_deleter> ptr;

Which results in:

 /usr/include/c++/4.8.2/tuple:132:22: error: no matching function for call to ‘mmap_deleter::mmap_deleter()’
   : _M_head_impl() { }
#include <memory>
#include <iostream>

#include <string.h>
#include <sys/mman.h>

constexpr auto PROT_RW = PROT_READ | PROT_WRITE;
constexpr auto MAP_ALLOC = MAP_PRIVATE | MAP_ANONYMOUS;

class mmap_deleter
{
    std::size_t m_size;

public:
    mmap_deleter(std::size_t size) :
        m_size{size}
    { }

    void operator()(void *ptr) const
    {
        munmap(ptr, m_size);
    }
};

template<typename T>
auto mmap_unique()
{
    if (auto ptr = mmap(0, sizeof(T), PROT_RW, MAP_ALLOC, -1, 0)) {

        auto obj = new (ptr) T(args...);
        auto del = mmap_deleter(sizeof(T));

        return std::unique_ptr<T, mmap_deleter>(obj, del);
    }

    throw std::bad_alloc();
}

struct myStruct{
    double foo;
    double bar;
};
// Forward declare pointer, neither compiles
std::unique_ptr<myStruct> ptr;
// or
// std::unique_ptr<myStruct,mmap_deleter> ptr;

int main()
{
    ptr = mmap_unique<myStruct>();
    ptr->foo = 55.;
    std::cout << ptr->foo << '\n';
}

Here's a silly example that will compile and run that uses a raw pointer that illustrates what I want to do with a smart pointer.

// myClass.h
class myClass
{
public:
    myClass();
    ~myClass();
    int get_i();
    int get_j();
private:
    void myFunc1();
    void myFunc2();
    void myFunc3();

    struct myStruct{
         int i;
         int j;
    };
// FORWARD Declaration of ptr here!!!!!!!!!!!!!!!!!!!!!
// I would like to use a smart pointer
    myStruct* ptr_myStruct{nullptr};
};
#include <sys/mman.h>
#include <iostream>
#include <string.h>
#include <cerrno>
//#include "myClass.h"
myClass::myClass(){
   // Set the pointer to the mmap'ed address
   ptr_myStruct = (myStruct*)mmap(NULL,sizeof(myStruct),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS, -1, 0);
   memset(ptr_myStruct,0,sizeof(myStruct));
   // The three member functions use ptr_myStruct
   myFunc1();
   myFunc2();
   myFunc3();
}

myClass::~myClass(){
   munmap(ptr_myStruct,sizeof(myStruct));
   ptr_myStruct = nullptr;
}

void myClass::myFunc1(){
ptr_myStruct->i++;
}
void myClass::myFunc2(){
ptr_myStruct->j++;
}
void myClass::myFunc3(){
ptr_myStruct->i++;
}
int myClass::get_i(){return ptr_myStruct->i;}
int myClass::get_j(){return ptr_myStruct->j;}
int main(){
  myClass a;
  std::cout<< a.get_i()<<"   "<<a.get_j()<<"\n";
}

Solution

  • After some massaging your code does compile and work (I added some debug prints to demonstrate it), see the live example. (Also, be aware that mmap_deleter doesn't call obj's destructor, but it should).

    Given that you use C++14, I would suggest to remove mmap_deleter and simplify the code as follows:

    template <typename T>
    using unique_mapped_ptr = std::unique_ptr<T, void(*)(T*)>;
    
    template<typename T, typename ...Args>
    unique_mapped_ptr<T> mmap_unique(Args... args)
    {
        constexpr auto PROT_RW = PROT_READ | PROT_WRITE;
        constexpr auto MAP_ALLOC = MAP_PRIVATE | MAP_ANONYMOUS;
    
        if (auto ptr = mmap(0, sizeof(T), PROT_RW, MAP_ALLOC, -1, 0)) {
            return {
                new (ptr) T{args...},
                [](T*p) {
                    p->~T();
                    munmap(p, sizeof(T));
                }
            };
        }
    
        throw std::bad_alloc();
    }
    

    You can then use unique_mapped_ptr<myStruct> to define pointers to mmapped objects.

    See the example with myClass.