I have an objcet in C++ which I am exporting via extern "c"
as a pointer. In order to be able to create the object and return it I must do it on the heap, given the scoping issue of trying to return locally scoped variables. However, in order to allocate on the heap, I must be able to copy an object, which I am strugling to do. Heres an example:
#include <iostream>
using namespace std;
// used to simulate complex data structure in nex object
class DataStorage {
private:
int a;
public:
explicit DataStorage(int a) : a(a) {
}
};
// object for export "C". Contains some pointers.
class NonTrivialObject {
public:
int *int_ptr;
char *char_ptr;
double *double_ptr;
DataStorage *data_storage_ptr;
NonTrivialObject(int *int_ptr, char *char_ptr, double *double_ptr, DataStorage *data_storage_ptr)
: int_ptr(int_ptr), char_ptr(char_ptr), double_ptr(double_ptr), data_storage_ptr(data_storage_ptr) {}
~NonTrivialObject() {
/*
* only delete objects of allocated on heap
*/
}
/*
* Copy constructor
*/
NonTrivialObject(const NonTrivialObject &rhs) {
if (this != &rhs) {
this->int_ptr = rhs.int_ptr;
this->char_ptr = rhs.char_ptr;
this->double_ptr = rhs.double_ptr;
this->data_storage_ptr = rhs.data_storage_ptr;
}
}
/*
* Copy assignment constructor
*/
NonTrivialObject &operator=(const NonTrivialObject &rhs) {
if (this != &rhs) {
this->int_ptr = rhs.int_ptr;
this->char_ptr = rhs.char_ptr;
this->double_ptr = rhs.double_ptr;
this->data_storage_ptr = rhs.data_storage_ptr;
}
return *this;
}
/*
* Move assignment constructor
*/
NonTrivialObject &operator=(NonTrivialObject &&rhs) noexcept {
if (this != &rhs) {
this->int_ptr = rhs.int_ptr;
this->char_ptr = rhs.char_ptr;
this->double_ptr = rhs.double_ptr;
this->data_storage_ptr = rhs.data_storage_ptr;
}
return *this;
}
/*
* Move constructor
*/
NonTrivialObject(NonTrivialObject &&rhs) noexcept {
if (this != &rhs) {
this->int_ptr = rhs.int_ptr;
this->char_ptr = rhs.char_ptr;
this->double_ptr = rhs.double_ptr;
this->data_storage_ptr = rhs.data_storage_ptr;
}
}
};
extern "C" {
// first method will not work because nonTrivialObjectPtr is locally scoped
NonTrivialObject *CopyANonTrivialObject1(NonTrivialObject obj) {
auto *nonTrivialObjectPtr = (NonTrivialObject *) malloc(sizeof(obj));
nonTrivialObjectPtr = &obj;
return nonTrivialObjectPtr;//Address of local variable may escape the function
}
// Only creates a local copy, presumably due to the contents of copy operator
NonTrivialObject *CopyANonTrivialObject2(NonTrivialObject obj) {
auto *nonTrivialObjectPtr = (NonTrivialObject *) malloc(sizeof(obj));
*nonTrivialObjectPtr = obj;
return nonTrivialObjectPtr;
}
}
int main() {
int i = 3;
char c = 's';
double dub = 3.98;
DataStorage dataStorage(4);
NonTrivialObject nonTrivialObject(&i, &c, &dub, &dataStorage);
NonTrivialObject* nonTrivialObject2 = CopyANonTrivialObject2(nonTrivialObject);
cout << nonTrivialObject.int_ptr << ", " << *nonTrivialObject.int_ptr << endl;
cout << nonTrivialObject2->int_ptr << ", " << *nonTrivialObject2->int_ptr << endl;
free(nonTrivialObject2);
return 0;
};
Will output
0x7ffd59c4ac28, 3
0x7ffd59c4ac28, 3
Indicating that the copy was a shallow copy. I'm aware that
/*
* Copy constructor
*/
NonTrivialObject(const NonTrivialObject &rhs) {
if (this != &rhs) {
this->int_ptr = rhs.int_ptr;
this->char_ptr = rhs.char_ptr;
this->double_ptr = rhs.double_ptr;
this->data_storage_ptr = rhs.data_storage_ptr;
}
}
is the issue, but in persuit of fixing it I keep getting segmentation faults. I tried various forms of dereferencing and taking the memory address as well as trying to replace the assignment with memcpy
and std::copy
, both of which were quite unpopular with valgrind.
How would I modify this class to be fully copyable, such that the memory locations holding the data are different but the values are the same?
How to properly implement a copy constructor of a class that has raw pointers?
It depends.
Does the class own the pointed objects? If there is no ownership involved, and the class just points to objects whose lifetime is not bound to the class, then simply copy the pointer. Note that because the class is not in control of the lifetime of the pointed object in this case, you must be very careful to ensure that the pointed object has a longer lifetime than the class instance that points to it. This is called a shallow copy.
If the class does own the object and is therefore responsible for its lifetime, then don't use raw pointers in the first place. Instead, use a smart pointer or a container. But if you were to use raw pointers (but don't), then you would dynamically allocate copies of the pointed objects. This is called a deep copy.
Don't use malloc and free in C++.