I want a class Watchdog
to create its objects in POSIX shared memory.
Keep in mind source code for this class is compiled into a static library and the source code for the methods are actually placed in a c++ source file.
My first attempt at this was having a static Watchdog* createWatchdog(param p1, param p2, ...)
function in my class, and use this as a wrapper for allocating the memory of the object, then calling a placement new (ptr) Watchdog
.
This worked fine but then I remembered the new
operator could be overloaded so I came up with this solution:
Watchdog.h
#pragma once
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
namespace og {
class Watchdog {
public:
Watchdog();
Watchdog(const std::string t_name, void* t_data, const size_t t_datSize);
static void* operator new(size_t count, const std::string t_name, const size_t t_datSize);
protected:
Watchdog(const Watchdog&) = delete;
Watchdog& operator=(const Watchdog&) = delete;
private:
std::string m_name;
void* m_data;
size_t m_datSize;
// and other member variables
}; // end class Watchdog
} // end namespace
Watchdog.cxx
#include "Watchdog.h"
using namespace og;
Watchdog::Watchdog()
{}
Watchdog::Watchdog(const std::string t, void* t_data, const size_t size)
{}
void* operator new(size_t count, const std::string t_name, const size_t dat)
{
int fd;
void* obj;
// removed all error checks for the sake of the example
fd = shm_open(t_name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0600);
(void) ftruncate(fd, sizeof(Watchdog));
obj = mmap(0x0, sizeof(Watchdog), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
(void) shm_unlink(t_name.c_str());
return obj;
}
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED( VERSION 2.6 )
PROJECT( WATCHDOG C CXX )
SET( CMAKE_CXX_STANDARD 11)
SET( CMAKE_CXX_STANDARD_REQUIRED OFF)
SET( watchdog_HEADERS
Watchdog.h
)
SET( watchdog_SRC
Watchdog.cxx
${watchdog_HEADERS}
)
SET( CMAKE_CXX_FLAGS_DEBUG "-g3 -Wall -Wextra -Wpedantic")
ADD_LIBRARY( watchdog STATIC ${watchdog_SRC} )
TARGET_LINK_LIBRARIES( watchdog -lrt )
Compiled with cmake . && make
main.cxx
#include "Watchdog.h"
using namespace og;
int
main()
{
Watchdog* W;
W = new ("test", 20) Watchdog;
}
g++ main.cxx -L. -lwatchdog -lrt
raises that linkage error:/usr/lib64/gcc/x86_64-suse-linux/9/../../../../x86_64-suse-linux/bin/ld: /tmp/ccDncNvb.o: in function `main':
main.cxx:(.text+0x3c): undefined reference to `og::Watchdog::operator new(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned long)'
collect2: error: ld returned 1 exit status
return ::operator new (sz)
or return ::operator new(sz, ptr)
from the operator new method but I fail to see how I can call instead a non-default constructor (such as the second one in the class specification).Question 1:
Your definition
void* operator new(size_t count, const std::string t_name, const size_t dat) //...
in Watchdog.cxx
defines a free user-defined operator new
, not the class-specific overload, that you declared in the class Watchdog
.
Instead use:
void* Watchdog::operator new(size_t count, const std::string t_name, const size_t dat) //...
Since the class-specific overload is chosen for the new-expression in main
, its definition will be missing.
Question 2:
operator new
only returns a pointer to raw memory. It is not responsible for constructing objects in it.
Objects are constructed by the new-expression with a call to a constructor as necessary. The syntax follows that of variable definitions. For example
W = new ("test", 20) Watchdog;
calls the default constructor for the Watchdog
object, while
W = new ("test", 20) Watchdog(arg1, arg2, arg3);
will try to call a constructor overload matching the three arguments.
The first parameter list in the new-expression is not used as arguments to the constructor, but as arguments that the allocation function needs in order to provide the correct pointer to memory in which the object can be constructed.
Also note, that the operator new
should allocate memory for (at least) count
(first argument) bytes. This argument will be provided with the correct value required to construct the object by the new-expression. This is in particular important when you are using the array versions of operator new
and new
.
Also be aware that you are constructing only the Watchdog
object itself in the shared memory provided by your operator new
overload. Should your class use new
to allocate memory for e.g. the void* m_data
member, it will not use your operator new
for the allocation, but the regular allocation function, which will allocate (as usual) in the processes (unshared) memory space.
The same applies to memory allocated by non-trivial members of your class, such as std::string m_name
. When it is going to need to allocate memory (if SSO is insufficient), it will allocate the memory for the string data with the regular allocation function (in unshared memory), not your operator new
overload.
This means, for example, that m_name
cannot be safely used from the other process you are sharing memory with.