How to track memory usage using EASTL? - c++

The Electronic Arts EASTL library's east::allocator requires the user to implement a special new operator (as shown in this sample). This new operator has a const char* name, which is supposed to be used to log application-specific information about memory allocations performed EASTL containers. The EASTL Best Practices guide also mentions that one should "Name containers to track memory usage."
However, there does not appear to be any corresponding const char* name passed by eastl::allocator::deallocate, so there does not appear to be a built-in way to log memory deallocations.
How is this EASTL debugging functionality intended to be used? I think I must be missing something. An EA Software Engineer also gave a related presentation in 2015, where he showed some internal tools developed by EA for memory debugging, and it seems to suggest that they tracking both allocations and deallocations (for example, to ensure that the memory for "game level 1" is freed when "game level 2" begins).

The allocators by the EASTL are stateful and bound to the instance of the container; meaning they are defined at instance level:
What EASTL does is use a more familiar memory allocation pattern
whereby there is only one allocator class interface and it is used by
all containers. Additionally EASTL containers let you access their
allocators and query them, name them, change them, etc.
EASTL has chosen to make allocators not be copied between containers
during container swap and assign operations. This means that if
container A swaps its contents with container B, both containers
retain their original allocators. Similarly, assigning container A to
container B causes container B to retain its original allocator.
Containers that are equivalent should report so via operator==; EASTL
will do a smart swap if allocators are equal, and a brute-force swap
otherwise.
from https://github.com/questor/eastl/blob/master/doc/EASTL%20Design.html
So I would add a member to the allocator class and track the memory count inside it, like following:
#ifndef EASTL_CUSTOM_ALLOCATOR_H_
#define EASTL_CUSTOM_ALLOCATOR_H_
#include "new_implementation.hpp"
#include <EASTL/list.h>
#include <iostream>
#define DEBUG_MACRO
class EASTL_CustomAllocator {
public:
EASTL_CustomAllocator(const char* pName = EASTL_NAME_VAL(EASTL_ALLOCATOR_DEFAULT_NAME))
: m_pName(pName), m_totalAmountOfBytesAllocated(0) {
#ifdef DEBUG_MACRO
std::cout << m_pName << ": default construct allocator" << std::endl;
#endif
}
EASTL_CustomAllocator(const EASTL_CustomAllocator& x)
: m_pName(x.m_pName),
m_totalAmountOfBytesAllocated(x.m_totalAmountOfBytesAllocated) {
#ifdef DEBUG_MACRO
std::cout << m_pName << ": copy construct allocator" << std::endl;
#endif
}
EASTL_CustomAllocator(const EASTL_CustomAllocator& x, const char* pName)
: m_pName(pName),
m_totalAmountOfBytesAllocated(x.m_totalAmountOfBytesAllocated) {
#ifdef DEBUG_MACRO
std::cout << m_pName << ": copy construct allocator" << std::endl;
#endif
}
EASTL_CustomAllocator& operator=(const EASTL_CustomAllocator& x) {
#ifdef DEBUG_MACRO
std::cout << m_pName << ": copy assignment" << std::endl;
#endif
m_pName = x.m_pName;
m_totalAmountOfBytesAllocated = x.m_totalAmountOfBytesAllocated;
return *this;
}
void* allocate(size_t num_of_bytes, int flags = 0) {
m_totalAmountOfBytesAllocated += num_of_bytes;
void* p = ::new((char*)0, flags, 0, (char*)0, 0) char[num_of_bytes];
#ifdef DEBUG_MACRO
std::cout << m_pName << ": allocate " << num_of_bytes << " bytes" << " at: " << (void*) p << std::endl;
#endif
return p;
}
void* allocate(size_t num_of_bytes, size_t alignment, size_t offset, int flags = 0) {
m_totalAmountOfBytesAllocated += num_of_bytes;
void* p = ::new(alignment, offset, (char*)0, flags, 0, (char*)0, 0) char[num_of_bytes];
#ifdef DEBUG_MACRO
std::cout << m_pName << ": allocate " << num_of_bytes << " bytes" << " at: " << (void*) p << std::endl;
#endif
return p;
}
void deallocate(void* p, size_t num_of_bytes) {
m_totalAmountOfBytesAllocated -= num_of_bytes;
#ifdef DEBUG_MACRO
std::cout << m_pName << ": deallocate " << num_of_bytes << " bytes" << " at: " << (void*) p << std::endl;
#endif
delete[](char*)p;
}
const char* get_name() const {
return m_pName;
}
void set_name(const char* pName) {
m_pName = pName;
}
size_t get_totalAmountOfBytesAllocated() const {
return m_totalAmountOfBytesAllocated;
}
protected:
const char* m_pName; // Debug name, used to track memory.
size_t m_totalAmountOfBytesAllocated; // keeps track of the memory currently allocated
};
bool operator==(const EASTL_CustomAllocator& a, const EASTL_CustomAllocator& b) {
if (&a == &b) {
return true; // allocator a and b are equal if they are the same
}
else {
return false; // otherwhise, return false, because the state m_totalAmountOfBytesAllocated needs to be increased/decreased on splice and swap
}
}
bool operator!=(const EASTL_CustomAllocator& a, const EASTL_CustomAllocator& b) {
return false;
}
#endif /* EASTL_CUSTOM_ALLOCATOR_H_ */
Pass that custom allocator type as a template parameter to a eastl container like following (also you can set a instance with a user-defined name at construction and even later by set_allocator()):
eastl::list<int, EASTL_CustomAllocator>
list(EASTL_CustomAllocator("EASTL Some Name"));
But I'm not sure how the debugging functionality is intended to be used.

Related

Will moving from released pointers leak memory?

I have the following code:
std::unique_ptr<T> first = Get();
…
T* ptr_to_class_member = GetPtr(obj);
*ptr_to_class_member = std::move(*first.release());
Will this behave as expected with no copies, 1 move and without memory leak?
*ptr_to_class_member = std::move(*first.release());
just calls the move assignment operator of T with the object pointed to by first as argument. This may properly transfer some data, but delete is not called or the object so neither T::~T is executed nor does the memory of the object get freed.
In the example of T = std::string this would result in the backing storage of the string object properly being transfered from the rhs to the lhs of the move assignment, but dynamically allocated memory of size sizeof(std::string) would still be leaked.
For some classes the lack of a destructor invocation for the object could result in additional trouble, since move assignment simply needs to leave the rhs in an unspecified, but valid state which could still require freeing of additional resources.
You need to do
*ptr_to_class_member = std::move(*first);
first.reset();
in order to prevent memory leaks.
To show what's going wrong here, the following code implements prints for memory (de)allocation and special member functions:
#include <iostream>
#include <memory>
#include <new>
#include <utility>
struct TestObject
{
TestObject()
{
std::cout << "TestObject::TestObject() : " << this << '\n';
}
TestObject(TestObject&& other)
{
std::cout << "TestObject::TestObject(TestObject&&) : " << this << ", " << &other << '\n';
}
TestObject& operator=(TestObject&& other)
{
std::cout << "TestObject::operator=(TestObject&&) : " << this << ", " << &other << '\n';
return *this;
}
~TestObject()
{
std::cout << "TestObject::~TestObject() : " << this << '\n';
}
void* operator new(size_t size)
{
void* const result = ::operator new(size);
std::cout << "memory allocated for TestObject: " << result << '\n';
return result;
}
void operator delete(void* mem)
{
std::cout << "memory of TestObject deallocated: " << mem << '\n';
::operator delete(mem);
}
};
template<class Free>
void Test(Free free, char const* testName)
{
std::cout << testName << " begin -------------------------------------------\n";
{
auto ptr = std::make_unique<TestObject>();
std::cout << "object creation done\n";
free(ptr);
}
std::cout << testName << " end ---------------------------------------------\n";
}
int main()
{
TestObject lhs;
Test([&lhs](std::unique_ptr<TestObject>& ptr)
{
lhs = std::move(*ptr);
ptr.reset();
}, "good");
Test([&lhs](std::unique_ptr<TestObject>& ptr)
{
lhs = std::move(*ptr.release());
}, "bad");
}
Possible output:
TestObject::TestObject() : 0000009857AFF994
good begin -------------------------------------------
memory allocated for TestObject: 000001C1D5715EF0
TestObject::TestObject() : 000001C1D5715EF0
object creation done
TestObject::operator=(TestObject&&) : 0000009857AFF994, 000001C1D5715EF0
TestObject::~TestObject() : 000001C1D5715EF0
memory of TestObject deallocated: 000001C1D5715EF0
good end ---------------------------------------------
bad begin -------------------------------------------
memory allocated for TestObject: 000001C1D5715EF0
TestObject::TestObject() : 000001C1D5715EF0
object creation done
TestObject::operator=(TestObject&&) : 0000009857AFF994, 000001C1D5715EF0
bad end ---------------------------------------------
TestObject::~TestObject() : 0000009857AFF994
You can clearly see the destructor call and deallocation missing in the second case, which is the one matching the code you're asking about.

boost::interprocess::message_queue no message received in second process

I am using boost's message queue to write a basic class with just two char arrays, but the data is not being received in the second process is empty, even though get_num_msg() returns 1 before the read and returns 0 after reading. For debugging purposes I also tried writing and reading from the same process, and that worked fine. I am using the shared pointer because earlier while just reading and writing integers, it would not read the integer at the receiver unless it was declared as shared ptr.
AccessQueue
class AccessQueue {
public:
char name[64];
char action[64];
AccessQueue(char name[64], char action[64]) {
strcpy(this->name, name);
strcpy(this->action, action);
}
AccessQueue() {}
};
sending function
// std::shared_ptr<AccessQueue> p1;
this->p1.reset(new AccessQueue("asd", "vsq"));
try {
this->mq->send(&p1, sizeof(p1), 0);
} catch(boost::interprocess::interprocess_exception & ex) {
std::cout << ex.what() << std::endl;
}
receiving function
std::cout << this->mq->get_num_msg() << "\t" << this->mq->get_max_msg_size() << "\t" << this->mq->get_max_msg() << std::endl;
AccessQueue * a;
unsigned int priority;
boost::interprocess::message_queue::size_type recvd_size;
try {
this->mq->try_receive(&a, sizeof(AccessQueue), recvd_size, priority);
} catch(boost::interprocess::interprocess_exception & ex) {
std::cout << ex.what() << std::endl;
}
std::cout << this->mq->get_num_msg() << "\t" << this->mq->get_max_msg_size() << "\t" << this->mq->get_max_msg() << std::endl;
std::cout << "It clearly maybe works " << a->action << "\t" << a->name << std::endl;
output at receiver's end:
1 128 20
0 128 20
Looks like p1 (in the sending function) is a smart pointer (like std::unique_ptr or std::shared_ptr). In that case
this->mq->send(&p1, sizeof(p1), 0);
is obviously wrong, because it puts the pointer object on the queue, instead of the data structure. Use
this->mq->send(*p1, sizeof(*p1), 0);
Or, indeed, don't use dynamic allocation in the first place:
AccessQueue packet("asd", "vsq");
mq.send(&packet, sizeof(packet), 0);
Uhoh there's more
On the receiving side, there's a similar problem:
AccessQueue * a;
// ..
mq.try_receive(&a, sizeof(AccessQueue), ...);
That receives INTO the pointer, not the object. You don't even have an object, because a (the pointer) is never initialized. Here the fix is syntactically simple:
AccessQueue a;
No more pointers. Now, a is an object and &a is the address of that object.
Note how the original was UB because you read sizeof(AccessQueue) bytes into a pointer. However the pointer is only 8 bytes and the struct is 128 bytes. Ooops!
Simplified Working Demo
This works:
Live On Wandbox¹
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iostream>
#include <iomanip>
namespace bip = boost::interprocess;
using MQ = bip::message_queue;
template<size_t N>
static inline void safe_copy(std::array<char, N>& dst, std::string_view src) {
std::copy_n(src.data(), std::min(src.size(), N), dst.data());
dst.back() = 0; // make sure of NUL termination
}
struct AccessQueue {
std::array<char, 64> name{0};
std::array<char, 64> action{0};
AccessQueue(std::string_view n = "", std::string_view a = "") {
safe_copy(name, n);
safe_copy(action, a);
}
};
static_assert(std::is_standard_layout_v<AccessQueue>);
struct X {
void send() {
AccessQueue packet("asd", "vsq");
try {
mq.send(&packet, sizeof(packet), 0);
} catch(std::exception const & ex) {
std::cout << ex.what() << std::endl;
}
}
AccessQueue receive() {
AccessQueue retval;
report();
try {
unsigned int priority;
MQ::size_type recvd_size;
mq.try_receive(&retval, sizeof(AccessQueue), recvd_size, priority);
} catch(std::exception const & ex) {
std::cout << ex.what() << std::endl;
}
report();
return retval;
}
void report() {
std::cout << mq.get_num_msg() << "\t" << mq.get_max_msg_size() << "\t" << mq.get_max_msg() << std::endl;
}
MQ mq { bip::open_or_create, "somequeue", 10, sizeof(AccessQueue) };
};
int main() {
X tryit;
tryit.send();
auto const& [name, action] = tryit.receive();
std::cout << std::quoted(name.data()) << " " << std::quoted(action.data()) << std::endl;
}
Prints
1 128 10
0 128 10
"asd" "vsq"
Note
using std::array over C arrays gives you copy semantics by default
guard the POD-ness of AccessQueue
make sure the members are initialized
make sure the copies are safe
make sure the copies are NUL-terminated always
Don't use new or delete. Why should C++ programmers minimize use of 'new'?
make sure your receive buffer size matches the max_msg_size (boost interprocess message_queue and fork)
¹ shared emmory is prohibited on Wandbox :(

Any way to detect whether an object of my class is create on stack?

Now I need to detect whether my class is created as a stack/global/thread_local variable, for example:
class Foo {
public:
Foo() {
if(im_on_stack) {
std::cout << "I'm on stack" << std::endl;
} else if(im_in_global) {
std::cout << "I'm in global" << std::endl;
} else if(im_a_thread_local) {
std::cout << "I'm a thread_local" << std::endl;
} else {
std::cout << "I'm on ohters location" << std::endl;
}
}
};
class Bar {
Foo mFoo;
};
Foo gFoo;
thread_local Foo tFoo;
int main() {
Foo lFoo;
}
and the out put should be:
I'm on ohters location
I'm in global
I'm a thread_local
I'm on stack
This there any way in C++ I can do this?
Edit:
why I'm doing this:
I'm writing a garbage collection library, and I got a class, let's call it gc_ptr, I need to know if this gc_ptr is a gc root (which is create on the location I mentioned) or not (which is a member of another class)
Edit2:
According to the concept of gc root, which is a reference which is not on a heap, I should probably asked in this way: can I detect if my class is create on the heap? But I think on heap or on stack make this question no difference.
Short answer: No. Not with standard C++. There may be compiler or OS specific solutions, but nothing portable.
I guess you could make a heuristic to detect stack allocated objects by detecting in their constructor whether their address is close to a stack allocated variable. Assuming that the stack and the heap have completely different memory addresses it should work. Completely undefined behaviour though according to the standard. e.g.:
#include <iostream>
#include <memory>
struct A
{
A()
{
int test = 0; // test is on the stack
auto distance = reinterpret_cast<char*>(this) - reinterpret_cast<char*>(&test);
isStack = std::abs(distance) < 1024; // completely arbitrary magic number, will need to experiment
}
bool isStack;
};
int main()
{
std::cout << "stack: " << A().isStack << "\n";
std::cout << "stack: " << std::make_unique<A>()->isStack << "\n";
}
I don't think you could expand this technique to thread local variables. You'd also need to take care in copy constructors and assignment operators to handle copying from a stack to a heap object and vice versa.
This is not part of the spec, so no.
If you told us on what system/os you are working, maybe we can assist - some systems may provide the stack address and size - usually this is available in embedded devices as part of the compiler output (the address), and as input (the size) as part of the environment/project configuration.
What I wrote in my comment:
Can be done to some extent: Assuming, you need it only for a certain kind of classes: You have to overload new (or wrap dynamic construction in a static create). You have to derive all considered classes from a base class with a specific constructor. To pass info from new to the constructor is a bit tricky. In our case, we used a global set where new remembered pointers to created instances and the corresponding constructor looked into it to determine whether creation was done by new. That was sufficient for us. (About the other topics - no idea...)
A demo:
#include <iostream>
#include <set>
class Object {
private:
static thread_local std::set<void*> pNewSet;
bool _isNewed;
public:
Object();
Object(const Object&) = delete;
const Object& operator=(const Object&) = delete;
~Object() = default;
static void* operator new(size_t size);
bool isNewed() const { return _isNewed; }
private:
static std::set<void*>& getNewPtrs()
{
static thread_local std::set<void*> pNewSet;
return pNewSet;
}
};
void* Object::operator new(size_t size)
{
std::set<void*> &pNewSet = getNewPtrs();
void *p = ::operator new(size);
if (p) pNewSet.insert(p);
return p;
}
Object::Object(): _isNewed(false)
{
std::set<void*> &pNewSet = getNewPtrs();
std::set<void*>::iterator iter = pNewSet.find((void*)this);
if (iter != pNewSet.end()) {
_isNewed = true;
pNewSet.erase(iter);
}
}
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
// a global static instance
static Object o;
int main()
{
DEBUG(std::cout << o.isNewed() << '\n');
// a static instance (local scope)
DEBUG(static Object o1);
DEBUG(std::cout << o1.isNewed() << '\n');
// a local instance
DEBUG(Object o2);
DEBUG(std::cout << o2.isNewed() << '\n');
// as members
DEBUG(struct Composed { Object o1, o2; } comp);
DEBUG(std::cout << comp.o1.isNewed() << ' ' << comp.o2.isNewed() << '\n');
// created with new
DEBUG(Object *pO = new Object());
DEBUG(std::cout << pO->isNewed() << '\n');
DEBUG(delete pO);
// created as members in an object created with new
DEBUG(Composed *pComp = new Composed());
DEBUG(std::cout << pComp->o1.isNewed() << ' ' << pComp->o2.isNewed() << '\n');
DEBUG(delete pComp);
}
Output:
std::cout << o.isNewed() << '\n';
0
static Object o1;
std::cout << o1.isNewed() << '\n';
0
Object o2;
std::cout << o2.isNewed() << '\n';
0
struct Composed { Object o1, o2; } comp;
std::cout << comp.o1.isNewed() << ' ' << comp.o2.isNewed() << '\n';
0 0
Object *pO = new Object();
std::cout << pO->isNewed() << '\n';
1
delete pO;
Composed *pComp = new Composed();
std::cout << pComp->o1.isNewed() << ' ' << pComp->o2.isNewed() << '\n';
0 0
delete pComp;
Live Demo on Compiler Explorer
Notes:
The copy constructor and assignment operator of Object are deleted intenionally.
Derived classes may provide a copy constructor but it has to call Object::Object().
The sample doesn't consider other flavors of new (e.g. operator new[] or the align variants since C++17). That should be done in productive code.
No, but you can prevent creation of objects on heap/stack.
To prevent creation on stack make the destructor private/protected:
class heap_only
{
public:
void release() const { delete this; }
protected:
~heap_only() {}
};
struct heap_only_deleter
{
void operator()( heap_only* p ) { p->release(); }
};
using up_heap_only = std::unique_ptr<heap_only, heap_only_deleter>;
//...
heap_only ho; // fails
heap_only* pho = new heap_only();
pho->release();
up_heap_only up{ new heap_only() };
To prevent creation on heap make the new operators private/protected:
class stack_only
{
protected:
static void* operator new( std::size_t );
static void* operator new[]( std::size_t );
};
//...
stack_only* pso = new stack_only(); // fails
stack_only so;
Regarding your edits, and just for fun:
void* pstart;
bool is_on_stack( void* p )
{
int end;
return pstart >= p && p > &end;
}
int globalint;
int main()
{
//
int start;
pstart = &start;
//
int stackint;
std::cout << std::boolalpha
<< is_on_stack( &globalint ) << std::endl
<< is_on_stack( &stackint ) << std::endl
<< is_on_stack( new int );
//...
}

Using shared_ptr with FreeRTOS queue

I use ESP-32 and need to pass std::shared_ptr using FreeRTOS queue. However, it loose one link. I think that this is source of a problem:
#include <iostream>
#include <memory>
#define PRINT_USE_COUNT(p) std::cout << "Use count: " << p.use_count() << std::endl;
extern "C" {
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
}
class testClass {
public:
testClass() {
std::cout << "Class is constructed" << std::endl;
};
virtual ~testClass() {
std::cout << "Class is destructed" << std::endl;
};
};
struct container {
std::shared_ptr<testClass> field;
};
extern "C" void app_main(void) {
auto queue = xQueueCreate(1, sizeof(container));
auto p = std::make_shared<testClass>();
PRINT_USE_COUNT(p); // 1
{
container c;
c.field = p;
PRINT_USE_COUNT(p); // 2
xQueueSendToBack(queue, &c, 0);
PRINT_USE_COUNT(p); // 2
}
PRINT_USE_COUNT(p); // 1 (Ooops!)
{
container c;
assert(xQueueReceive(queue, &c, 0) == pdTRUE);
PRINT_USE_COUNT(c.field); // 1
}
// Class is destructed
std::cout << "Test finished" << std::endl;
vQueueDelete(queue);
}
So there is a pointer in queue, but it isn't counted!
How can I solve this issue (and keep using FreeRTOS queue if possible)? Using std::move doesn't help.
A C-style queue of raw pointers will only works for C++ shared_ptr iff std::is_trivial<T>::value is true (mainly POD or trivially copyable object).
Since there are memcpy and other plain C operation manipulating memory the reference count will not be handled properly (because it is C-code behind the scene and it does not call destructor among other thing) and you could end up with a memory leak.
There is no easy way to circumvent this, but the best way is to manage the memory yourself.
See this question also : Shared pointers and queues in FreeRTOS
I managed to transform unique pointer into a raw format which could be sent via a message queue.
See here: https://codereview.stackexchange.com/questions/241886/using-unique-ptr-in-freertos.
Please note I posted it in code review because I am not sure whether there are really no memory leaks or this can be implemented much more cleanly.
I will test whether I can actually use this for the IPC in our project.
I don't always agree that developing something from scratch is best option. Using something that is well tested might be best option in most occasions even-though it could require some tweaking for making it fit to your needs.
With the queue you can pass a dynamically created instance of your container. It is very rare, if it is at all, to use a queue for sending data from one task to same task, as the example above. I don't like pretty much working with dynamic allocations in embedded CPUs, the overhead can sometimes impact performance too much.
Here below is a working PoC where, instead of a raw copy, a pointer to a new container instance is passed. In this approach it is the responsibility of the receiving task to release the instance to avoid memory leaks.
extern "C" {
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
}
#include <iostream>
#include <memory>
#define PRINT_USE_COUNT(p) std::cout << "Use count: " << p.use_count() << std::endl;
class testClass {
public:
testClass() {
std::cout << "testClass constructed" << std::endl;
}
~testClass() {
std::cout << "testClass destructed" << std::endl;
}
};
class myContainer {
public:
myContainer(std::shared_ptr<testClass> p) {
_p = p;
std::cout << "myContainer constructed" << std::endl;
}
~myContainer() {
std::cout << "myContainer destructed" << std::endl;
}
std::shared_ptr<testClass>& p() {
return _p;
}
private:
std::shared_ptr<testClass> _p;
};
extern "C" void app_main(void) {
std::cout << "Start of test, creating the shared_ptr..." << std::endl;
auto p = std::make_shared<testClass>();
PRINT_USE_COUNT(p);
std::cout << "Creating one container..." << std::endl;
myContainer c(p);
PRINT_USE_COUNT(p);
std::cout << "Creating the queue..." << std::endl;
auto q = xQueueCreate(1, sizeof(myContainer*));
std::cout << "Sending a dynamically created item to the queue..."
<< std::endl;
myContainer *cp = new myContainer(p);
xQueueSendToBack(q, &cp, 0);
PRINT_USE_COUNT(p);
{
myContainer *pc;
xQueueReceive(q, &pc, 0);
PRINT_USE_COUNT(p);
std::cout << "Use count of pc->p() " << pc->p().use_count()
<< std::endl;
std::cout << "Freeing the dynamically created item..." << std::endl;
delete pc;
PRINT_USE_COUNT(p);
}
std::cout << "end of test" << std::endl;
}
Here's the program's output:
Start of test, creating the shared_ptr...
testClass constructed
Use count: 1
Creating one container...
myContainer constructed
Use count: 2
Creating the queue...
Sending a dynamically created item to the queue...
myContainer constructed
Use count: 3
Use count: 3
Use count of pc->p() 3
Freeing the dynamically created item...
myContainer destructed
Use count: 2
end of test
myContainer destructed
testClass destructed

How do I move multiple parameter via Move semantics?

Let us assume I have an Object MeasurementValues, which has n different pointers (this examples just show pointers to primitive types, but pointers to other complex objects mivght occur as well).
class MesaurementValues {
private:
int *measurement_1;
double *measurement_2;
long long *measurement_3;
//..
float *measurement_n;
int noPointer;
}
I know, this example might be a little bit contrived, anyway. I try to fullfill the Rule of Five in my code.
Do I have to
this.measurement_x = old.measurement_x ;// for all x = {1,..,n} ?
First off just be aware that move-semantics give no advantage over copy-semantics for plain-old-data type (POD) members, but it sure does when your class contains other class objects and/or arrays. When you implement move-semantics, it means you have a "move constructor" and/or a "move assignment operator" which would look like this in your class:
class MesaurementValues { private:
int *measurement_1;
double *measurement_2;
long long *measurement_3;
//..
float *measurement_n;
int noPointer;
//a couple different objects
someObject* pObj1;
differentObject* pObj2;
public:
MeasurementValues( MeasurementValues&& move ); //move-constructor
MeasurementValues& operator= (MeasurementValues&& move); //move-assignment
}
-Assuming your class has POD data and class objects, and
-assuming there are alot of variables to move over:
MeasurementValues::MeasurementValues( MeasurementValues&& old) {
//copy plain-old-data over
measurement_1 = old.measurement_1;
measurement_2 = old.measurement_2;
//copy over values of the pointers
pObj1 = old.pObj1;
pObj2 = old.pObj2;
}
Keep in mind that move-semantics, as other posters have said, only have an advantage if your data members are other moveable objects, or dynamically-allocated memory.
EDIT:
pointers in must become "invalid", as they've been moved. Therefore I would set them to null to prevent unexpected behavior:
MeasurementValues::MeasurementValues( MeasurementValues&& old)
: measurement_1() //null...
//,...
{
//Swapping null into old...
std::swap(measurement_1, old.measurement_1);
//...
}
Do I have to
this.measurement_x = old.measurement_x ;// for all x = {1,..,n} ?
I would rely on the pimpl idiom for this, and use something like a unique pointer. Below is a detailed example. Note you only have to work with Impl, and rely on its defaults (as it contains no pointers).
#include <iostream>
#include <memory>
struct Moveable
{
public:
Moveable();
~Moveable();
Moveable(const Moveable& m);
Moveable& operator=(const Moveable& m);
Moveable(Moveable&& m);
Moveable& operator=(Moveable&& m);
int foo() const;
private:
struct Impl;
std::unique_ptr<Impl> pimpl_;
};
//Moveable.cpp
struct Moveable::Impl
{
Impl(): a(1), b(2), c(3), buffer(){}
int a, b, c;
char buffer[10000]; //Make it worth our while...
};
int Moveable::foo() const{ return pimpl_->a+pimpl_->b+pimpl_->c;}
Moveable::Moveable()
: pimpl_(new Impl)
{
std::cout << "Default " << (void*)this << std::endl;
}
Moveable::~Moveable()
{
std::cout << "Destruct " << (void*)this << std::endl;
//automagically...
}
Moveable::Moveable(const Moveable&m)
: pimpl_(new Impl(*m.pimpl_))
{
std::cout << "Copying " << &m << " to " << (void*)this << std::endl;
}
Moveable& Moveable::operator=(const Moveable& m)
{
std::cout << "Copy assign " << (void*)&m << " to " << (void*)this << std::endl;
*pimpl_ = *m.pimpl_; //Relying on their defaults...
}
Moveable::Moveable(Moveable&& m)
: pimpl_(move(m.pimpl_))
{
std::cout << "Moving " << (void*)&m << " to " << (void*)this << std::endl;
}
Moveable& Moveable::operator=(Moveable&& m)
{
pimpl_ = move(m.pimpl_);
std::cout << "Move assigning " << &m << " to " << (void*)this << std::endl;
}
int main()
{
Moveable x;
Moveable y(x); //Copying...
y = x; //Copying assign
y = Moveable(); //Default construct, then move assignment, destruct temp
Moveable z(std::move(y));
std::cout << "Calling foo " << z.foo() << std::endl;
return 0;
}