how to disable move construct base class from derived class? - c++

In the following code, I want to disable the move construction of base class Vector from derived class VectorMap, and call the copy constructor.
#include <iostream>
#include<algorithm>
struct Vector{
int* _ptr=nullptr;
int _size=0;
Vector(int n){
_ptr = new int[n];
_size = n;
std::cout<< " Construct "<<this<<std::endl;
}
Vector(void) { std::cout <<" Construct " << this << std::endl; }
virtual ~Vector(void) {
if (_ptr != nullptr) {
std::cout << "Deconstruct " << this << " -> delete " << _ptr << std::endl;
delete _ptr;
return;
}
std::cout << "Deconstruct " << this << std::endl;
}
Vector(Vector&& v2) noexcept {
int* p2=v2._ptr; int s2=v2._size;
v2._ptr=_ptr;
v2._size=_size;
_ptr=p2; _size=s2;
std::cout << "Move construct " << this << std::endl;
}
Vector(const Vector& v3){
_ptr=new int[v3._size];
_size=v3._size;
memcpy(_ptr,v3._ptr,sizeof(int) * _size);
}
};
struct VectorMap
: public Vector {
VectorMap(int* p,int size){
_ptr=p;
_size=size;
}
~VectorMap(void) override {
_ptr=nullptr; _size=0;
}
};
int main(void) {
Vector v1(10);
Vector v2=VectorMap(v1._ptr,5); // v1._ptr will be deleted twice
return sizeof(v2);
}
As you can see, if move constructor is called in the line Vector v2=VectorMap(v1._ptr,5);, the data pointer in v1 will be deleted twice, one is by v2, and another is by v1. Since they share the same pointer. Is there any way to modify VectorMap to call copy constructor rather than move constructor in such case?

The primary problem is muddy ownership semantics.
Vector creates a resource (dynamic array), and is apparently intended to have unique ownership over it.
VectorMap on the other hand takes a pointer in its constructor, and gives it to its base Vector that takes ownership. But right before destruction, VectorMap rescinds the ownership by setting the base Vector pointer to null. So, VectorMap sort of pretends to own the resource until it's time for the responsibility of the ownership at which point it backs down.
This inheritance also causes other problem situations. Consider for example making a copy of VectorMap. Look at what the copy constructor of the base does. It allocates memory for the copy. But the desturctor of the VectorMap copy sets the pointer to null, so in this case the pointer is deleted zero times. It leaks memory.
As an analogy, disabling slicing would be a bandage for the wound, but what you really should do is to not stick your hand inside a running blender. If VectorMap is supposed to not have ownership, then it shouldn't inherit a base that takes ownership. It's unclear to me what the point of VectorMap class even is.
Furthermore, a class that has unique ownership of a resource such as Vector really should encapsulate that bare pointer with private access specifier. Sure, such classes sometimes still provide a way to copy or throw away that value (like std::vector::data and std::unique_ptr::release), but it's important that only happens through a specific function to reduce chance of accidental violation of ownership semantics.
Another serious bug:
_ptr=new int[v3._size];
// ...
delete _ptr;
You must not delete a pointer to a dynamic array. You must use delete[]. Using delete results in undefined behaviour.
Another bug: You forgot to include the header that declares memcpy. Also, I would recommend using std::copy instead.

Related

C++ struct with dynamically allocated char arrays

I'm trying to store structs in a vector. Struct needs to dynamically allocate memory for char* of a given size.
But as soon as I add the struct to a vector, its destructor gets called, as if I lost the pointer to it.
I've made this little demo for the sake of example.
#include "stdafx.h"
#include <iostream>
#include <vector>
struct Classroom
{
char* chairs;
Classroom() {} // default constructor
Classroom(size_t size)
{
std::cout << "Creating " << size << " chairs in a classroom" << std::endl;
chairs = new char[size];
}
~Classroom()
{
std::cout << "Destroyng chairs in a classroom" << std::endl;
delete[] chairs;
}
};
std::vector<Classroom> m_classrooms;
int main()
{
m_classrooms.push_back(Classroom(29));
//m_classrooms.push_back(Classroom(30));
//m_classrooms.push_back(Classroom(30));
system("Pause");
return 0;
}
The output is
Creating 29 chairs in a classroom
Destroyng chairs in a classroom
Press any key to continue . . .
Destroyng chairs in a classroom
Yes, seems like the destructor gets called twice! Once upon adding to a vector, and second time upon the program finishing its execution.
The exact same thing happens when I try to use a class instead of a struct.
Can someone explain why this happens and what are the possible ways to accomplish my task correctly?
The Classroom class cannot be used in a std::vector<Classroom> safely because it has incorrect copy semantics. A std::vector will make copies of your object, and if the copy semantics have bugs, then you will see all of those bugs manifest themselves when you start using the class in containers such as vector.
For your class to have correct copy semantics, it needs to be able to construct, assign, and destruct copies of itself without error (those errors being things like memory leaks, double deletion calls on the same pointer, etc.)
The other thing missing from your code is that the size argument needs to be known within the class. Right now, all you've posted is an allocation of memory, but there is nothing that saves the size. Without knowing how many characters were allocated, proper implementation of the user-defined copy constructor and assignment operator won't be possible, unless that char * is a null-terminated string.
Having said that, there a multiple ways to fix your class. The easiest way is to simply use types that have correct copy semantics built into them, instead of handling raw dynamically memory yourself. Those classes would include std::vector<char> and std::string. Not only do they clean up themselves, these classes know their own size without having to carry a size member variable.
struct Classroom
{
std::vector<char> chairs;
Classroom() {} // default constructor
Classroom(size_t size) : chairs(size)
{
std::cout << "Creating " << size << " chairs in a classroom" << std::endl;
}
};
The above class will work without any further adjustments to it, since std::vector<char> has correct copy semantics already. Note that there is no longer a need for the destructor, since std::vector knows how to destroy itself.
If for some reason you had to use raw dynamically allocated memory, then your class has to implement a user-defined copy constructor, assignment operation, and destructor.
#include <algorithm>
struct Classroom
{
size_t m_size;
char* chairs;
// Note we initialize all the members here. This was a bug in your original code
Classroom() : m_size(0), chairs(nullptr)
{}
Classroom(size_t size) : m_size(size), chairs(new char[size])
{}
Classroom(const Classroom& cRoom) : m_size(cRoom.m_size),
chairs(new char[cRoom.m_size])
{
std::copy(cRoom.chairs, cRoom.chairs + cRoom.m_size, chairs);
}
Classroom& operator=(const Classroom& cRoom)
{
if ( this != &cRoom )
{
Classroom temp(cRoom);
std::swap(temp.m_size, m_size);
std::swap(temp.chairs, chairs);
}
return *this;
}
~Classroom() { delete [] chairs; }
};
Note the usage of the member-initialization list when initializing the members of the class. Also note the usage of the copy / swap idiom when implementing the assignment operator.
The other issue that was corrected is that your default constructor was not initializing all of the members. Thus in your original class a simple one line program such as:
int main()
{
Classroom cr;
}
would have caused issues, since in the destructor, you would have deleted an uninitialized chairs pointer.
After this, a std::vector<Classroom> should now be able to be safely used.
#LPVOID
Using emplace_back(..) to create the object in place can help you avoid the double free or corruption error you are facing here.
m_classrooms.emplace_back(29)
However, it is a better practice to always follow the rule of 3/5/0 to not end up with a dangling pointer.

How to pass the ownership of an object to the outside of the function

Is there any method that can pass the ownership of an object created in the function on the stack memory to the outside of the function without using copy construction?
Usually, compiler will automatically call destruction to the object on the stack of a function. Therefore, if we want to create an object of a class(maybe with some specific parameters), how can we avoid wasting lots of resources copying from temp objects?
Here is one common case:
while(...){
vectors.push_back(createObject( parameter ));
}
So when we want to create objects in a iteration with some parameters, and push them into a vector, normal value passed way will take a lot of time copying objects. I don't want to use pointer and new objects on the heap memory as user are likely to forget delete them and consequently cause memory leak.
Well, smart pointer maybe a solution. But..less elegent, I think. hhhh
Is there any way of applying rvalue reference and move semantics to solve this problem?
Typically, returning an object by value will not copy the object, as the compiler should do a (named) return value optimization and thereby elide the copy.
With this optimization, the space for the returned object is allocated from the calling context (outer stack frame) and the object is constructed directly there.
In your example, the compiler will allocate space for the object in the context where createObject() is called. As this context is an (unnamed) parameter to the std::vector<T>.push_back() member function, this works as an rvalue reference, so the push_back() by-value will consume this object by moving (instead of copying) it into the vector. This is possible since if the generated objects are movable. Otherwise, a copy will occur.
In sum, each object will be created and then moved (if moveable) into the vector.
Here is a sample code that shows this in more detail:
#include <iostream>
#include <string>
#include <vector>
using Params = std::vector<std::string>;
class Object
{
public:
Object() = default;
Object(const std::string& s) : s_{s}
{
std::cout << "ctor: " << s_ << std::endl;
}
~Object()
{
std::cout << "dtor: " << s_ << std::endl;
}
// Explicitly no copy constructor!
Object(const Object& other) = delete;
Object(Object&& other)
{
std::swap(s_, other.s_);
std::cout << "move: traded '" << s_ << "' for '" << other.s_ << "'" << std::endl;
}
Object& operator=(Object other)
{
std::swap(s_, other.s_);
std::cout << "assign: " << s_ << std::endl;
return *this;
}
private:
std::string s_;
};
using Objects = std::vector<Object>;
Object createObject(const std::string& s)
{
Object o{s};
return o;
}
int main ()
{
Objects v;
v.reserve(4); // avoid moves, if initial capacity is too small
std::cout << "capacity(v): " << v.capacity() << std::endl;
Params ps = { "a", "bb", "ccc", "dddd" };
for (auto p : ps) {
v.push_back(createObject(p));
}
return 0;
}
Note that the class Object explicitly forbids copying. But for this to work, the move constructur must be available.
A detailed summary on when copy elision can (or will) happen is available here.
Move semantics and copy elison of vectors should mean the elements of the local std::vector are in fact passed out of the object and into your local variable.
Crudely you can expect the move constructor of std::vector to be something like:
//This is not real code...
vector::vector(vector&& tomove){
elems=tomove.elems; //cheap transfer of elements - no copying of objects.
len=tomove.elems;
cap=tomove.cap;
tomove.elems=nullptr;
tomove.len=0;
tomove.cap=0;
}
Execute this code and notice the minimum number of objects are constructed and destructed.
#include <iostream>
#include <vector>
class Heavy{
public:
Heavy(){std::cout<< "Heavy construction\n";}
Heavy(const Heavy&){std::cout<< "Heavy copy construction\n";}
Heavy(Heavy&&){std::cout<< "Heavy move construction\n";}
~Heavy(){std::cout<< "Heavy destruction\n";}
};
std::vector<Heavy> build(size_t size){
std::vector<Heavy> result;
result.reserve(size);
for(size_t i=0;i<size;++i){
result.emplace_back();
}
return result;
}
int main() {
std::vector<Heavy> local=build(5);
std::cout<<local.size()<<std::endl;
return 0;
}
Move semantics and copy elison tend to take care of this problem C++11 onwards.
Expected output:
Heavy construction
Heavy construction
Heavy construction
Heavy construction
Heavy construction
5
Heavy destruction
Heavy destruction
Heavy destruction
Heavy destruction
Heavy destruction
Notice that I reserved capacity in the vector before filling it and used emplace_back to construct the objects straight into the vector.
You don't have to get the value passed to reserve exactly right as the vector will grow to accommodate values but it that will eventually lead to a re-allocation and move of all the elements which may be costly depending on whether you or the compiler implemented an efficient move constructor.

Does Deleting a Dynamically Allocated Vector Clear It's Contents

Say I have:
vector<string>* foo = new vector<string>();
I add a ton of stuff to it, use it, and then I just call:
delete foo;
Did I need to call foo.clear(); first? Or will the delete call the destructor.
Please no comments regarding the folly of this, I know that at a minimum auto-pointers should be used here. This behavior is in the code base I'm working in and it's outside scope for me to go fix it.
Yes, the vector's destructor will be called, and this will clear its contents.
delete calls the destructor before de-allocating memory, and vector's destructor implicitly calls .clear() (as you know from letting an automatic-storage duration vector fall out of scope).
This is quite easy to test, with a vector<T> where T writes to std::cout on destruction (though watch out for copies inside of the vector):
#include <vector>
#include <iostream>
struct T
{
T() { std::cout << "!\n"; }
T(const T&) { std::cout << "*\n"; }
~T() { std::cout << "~\n"; }
};
int main()
{
std::vector<T>* ptr = new std::vector<T>();
ptr->emplace_back();
ptr->emplace_back();
ptr->emplace_back();
delete(ptr); // expecting as many "~" as "!" and "*" combined
}
(live demo)
According to the requirements of containers (the C++ Standard, Table 96 — Container requirements)
(&a)->~X() - the destructor is applied to every element of a; all the memory is deallocated.
where X denotes a container class containing objects of type T, a and b denote values of type X,

std::vector::erase(iterator position) does not necessarily invoke the corresponding element's destructor

Assuming I have a std::vector V of 5 elements,
V.erase(V.begin() + 2) remove the 3rd element.
STL vector implementation will move 4th and 5th element up, and then destruct the 5th element.
I.e. erasing element i in a vector does not guarantee that ith destructor is called.
For std::list, this is not the case. Erasing ith element invokes ith element's destructor.
What does STL say about this behavior?
This is code taken from my system's stl_vector.h:
392 iterator erase(iterator __position) {
393 if (__position + 1 != end())
394 copy(__position + 1, _M_finish, __position);
395 --_M_finish;
396 destroy(_M_finish);
397 return __position;
The C++11 standard 23.3.6.5/4 says (emphasis is mine):
Complexity: The destructor of T is called the number of times equal to the number of the elements erased, but the move assignment operator of T is called the number of times equal to the number of elements in the vector after the erased elements.
Had the implementation called the destructor on the 3rd element, it wouldn't be conform.
Indeed, suppose that the destructor is called on the 3rd element. Since only one element is erased, the desctructor cannot be called again.
After the destructor call, the 3rd position contains raw memory (not a fully constructd object T). Hence the implementation needs to call the move constructor to move from the 4th position to the 3rd one.
It cannot destroy the 4th element (because it can no longer call the destructor) and then to move from the 5th to the 4th element it must call the move assignment operator.
At this point, the implementation still needs to decrease the vector size by 1 and destroy the 5th element but, as we have seen, no other destrucor call is allowed. (Notice also that the move assignement operator would not be called twice as required by the standard.) QED.
The standard says that's expected, the specification for vector::erase(const_iterator) (in the table of Sequence container requirements) says that the requirements on that function are:
For vector and deque, T shall be MoveAssignable.
The reason for requiring MoveAssignable is that each of the following elements will be (move) assigned over the element before them, and the last element destroyed.
In theory it would have been possible for the original STL to have done it differently and to have destroyed the erased element as you expect, but there are good reasons that wasn't chosen. If you only destroy the erased element you leave a "hole" in the vector, which isn't an option (the vector would have to remember where holes were and if a user says v[5] the vector would have to remember there's a hole there and return v[6] instead.) So it's necessary to "shuffle" the later elements down to fill the hole. That could have been done by destroying the Nth element in place (i.e. v[N].~value_type()) and then using placement new to create a new object at that location (i.e. ::new ((void*)&v[N]) value_type(std::move(v[N+1]))) and then doing the same for each following element, until you get to the end, however that would result in far worse performance in many cases. If the existing elements have allocated memory, e.g. are containers themselves, then assigning to them may allow them to reuse that memory, but destroying them and then constructing new elements would require deallocating and reallocating memory, which may be much slower and could fragment the heap. So there is a very good reason to us assignment to alter the elements' values, without necessarily altering their identities.
This isn't the case for std::list and other containers because they do not store elements in a contiguous block like vector and deque, so removing a single element just involves adjusting the links between the neighbouring elements, and there is no need to "shuffle" other elements down the block to take up the empty position.
This is perfectly valid behaviour. #Cassio Neri pointed out why it is required by the standard.
Short:
"std::vector::erase(iterator position) does not necessarily invoke the corresponding element's destructor" [Op; Headline] but a destructor is invoked, handling the data of the corresponding elements which has been transfered to another object (either via move constructor to the moved-from or via RAII to the temporary instance).
Long:
Why you don't have to rely on the ith destructor to be called.
I'll provide some hints why you shouldn't worry at all, which destructor is called in this case.
Consider the following small class
class test
{
int * p;
public:
test (void) : p(new int[5]) { cout << "Memory " << p << " claimed." << endl; }
~test (void) { cout << "Memory " << p << " will be deleted." << endl; delete p; }
};
If you handle your object move-assignment correctly there is no need to worry about the fact which destructor is called properly.
test& operator= (test && rhs)
{
cout << "Move assignment from " << rhs.p << endl;
std::swap(p, rhs.p);
return *this;
}
Your move assignment operator has to transfer the state of the object that is "overwritten" into the object that is "moved from" (rhs here) so it's destructor will take proper action (if there is something the destructor needs to take care of). Perhaps you should use something like a "swap" member function to do the transfer for you.
If your object is non-moveable you'll have to handle the "cleanup" (or whatever action that relies on the current state of the object) of the erased object in the copy assignment operation before you copy the new data into the object.
test& operator= (test const &rhs)
{
test tmp(rhs);
std::swap(p, tmp.p);
return *this;
}
Here we use RAII and again the swap (which may still be a member function, too; but test only has one pointer...). The destructor of tmp will make things cosy.
Let's do a small test:
#include <vector>
#include <iostream>
using namespace std;
class test
{
int * p;
public:
test (void) : p(new int[5]) { cout << "Memory " << p << " claimed." << endl; }
test& operator= (test && rhs)
{
cout << "Move assignment from " << rhs.p << endl;
std::swap(p, rhs.p);
return *this;
}
~test (void) { cout << "Memory " << p << " will be deleted." << endl; delete p; }
};
int main (void)
{
cout << "Construct" << endl;
std::vector<test> v(5);
cout << "Erase" << endl;
v.erase(v.begin()+2);
cout << "Kick-off" << endl;
return 0;
}
Results in
Construct
Memory 012C9F18 claimed.
Memory 012CA0F0 claimed.
Memory 012CA2B0 claimed. // 2nd element
Memory 012CA2F0 claimed.
Memory 012CA110 claimed.
Erase
Move assignment from 012CA2F0
Move assignment from 012CA110
Memory 012CA2B0 will be deleted. // destruction of the data of 2nd element
Kick-off
Memory 012C9F18 will be deleted.
Memory 012CA0F0 will be deleted.
Memory 012CA2F0 will be deleted.
Memory 012CA110 will be deleted.
Every memory location that is claimed will be released properly if your move (or copy) assignment operation hands over the critical properties to the object that will be destroyed.
Every destructor that relies on the internal state of an object will be called with the proper object around if your assignment operations are designed properly.
Unlike std::list, std::vector holds its elements contiguously. So when an element is erased from the middle of the container, it would make more sense to copy assign all elements that need to be shifted. In this scenario, the destructor of the last shifted element would be called. This avoids a re-allocation of the whole data of the vector.
In reference to example by Mats Petersson, perhaps this example will show more clearly that destroy 2 really happens, we just don't have destructor available for built-in type where we can conveniently add the printout statement:
#include <vector>
#include <iostream>
#include <utility>
using namespace std;
struct Integer
{
int x;
Integer(int v) : x(v) {}
~Integer() { cout << "Destroy Integer=" << x << endl; }
};
class X
{
Integer Int;
public:
X(int v) : Int(v) {}
X operator=(const X& a)
{
auto tmp(a.Int);
swap(this->Int, tmp);
cout << "copy x=" << Int.x << endl;
return *this;
}
};
int main()
{
vector<X> v;
for(int i = 0; i < 5; i++)
{
X a(i);
v.push_back(a);
}
cout << "Erasing ... " << endl;
v.erase(v.begin() + 2);
}
This will print:
Destroy Integer=0
Destroy Integer=0
Destroy Integer=1
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=3
Destroy Integer=0
Destroy Integer=1
Destroy Integer=2
Destroy Integer=3
Destroy Integer=4
Erasing ...
Destroy Integer=2
copy x=3
Destroy Integer=2
Destroy Integer=3
Destroy Integer=3
copy x=4
Destroy Integer=3
Destroy Integer=4
Destroy Integer=4
(skipped printout of destructor calls for entire vector on program exit)
One way of looking at this is to ask yourself: what does it mean to erase an object from a vector? It means that, given a way to identify that object, you won't be able to find it in a vector after the erase. Maybe it was a value that got overwritten, thereby acquiring a new identity. If it held resources that could identify it, those will be properly released, as others mentioned, as long as move, assignment and copy do the right thing. Additionally, the size of vector would reflect that there is one object less.
For your philosophical amusement, here are some notes by Stepanov (primary STL author):
Integral parts of an object are those parts of the object needed to
realize its primary purpose. Connections among integral parts
constitute the integral form of the object. Two intuitive constraints
that we have on the definition of essential parts are (i) for certain
objects, it is possible to take them apart which would result i n
their losing their identity and later they could be brought together
which would imply their regaining their identity. This allows objects
to exist, disappear and later reappear; thus there is a discontinuity
in their existence. (ii) some essential parts of an object can be
replaced one by one without the object losing its identity. To define
identity across time, we introduce the notion of essential parts and
essential form.
Definition: An essential part of an object is an integral part such that if it is removed, the object loses its identity, hence it
disappears.
Here's a small program that shows the problem, and yes, if you RELY on the destructor being called for that very object, you need to do something other than what this code does:
#include <iostream>
#include <vector>
using namespace std;
class X
{
int x;
public:
X(int v) : x(v) {}
~X() { cout << "Destroy v=" << x << endl; }
X operator=(const X& a) { x = a.x; cout << "copy x=" << x << endl; return *this; }
};
int main()
{
vector<X> v;
for(int i = 0; i < 5; i++)
{
X a(i);
v.push_back(a);
}
cout << "Erasing ... " << endl;
v.erase(v.begin() + 2);
}
The output is:
Destroy v=0
Destroy v=0
Destroy v=1
Destroy v=0
Destroy v=1
Destroy v=2
Destroy v=3
Destroy v=0
Destroy v=1
Destroy v=2
Destroy v=3
Destroy v=4
Erasing ...
copy x=3
Destroy v=3
copy x=4
Destroy v=4 <<< We expedct "destroy 2", not "destroy 4".
Destroy v=4
Destroy v=0
Destroy v=1
Destroy v=3
Destroy v=4
One variant to solve this would be to store a (smart) pointer, and manually copy out the pointer and then delete it.

C++ Class Copy (pointer copy)

It is my understanding that when you make a copy of a class that defines a pointer variable, the pointer is copied, but the data that the pointer is pointing to is not.
My question is: Is one to assume that the "pointer copy" in this case is simply instantiating a new pointer (dynamic memory allocation) of the same type? Eg., the new pointer is simply a new allocation containing an arbitrary memory address and one should take care to point that new pointer to the appropriate memory address?
I presume there is a quite simple answer to this question, and I apologize for its trivial nature, but I am trying to understand pointers at a deeper level and this came up upon my researching pointers on the internet.
Regards,
Chad
The pointer will be simply copied as a value - so both classes will point to the same original memory, no new allocation takes place. Shallow copy - this is what the language does by default.
If you need to allocate new memory and make a copy of the data you have to do that yourself in the copy constructor. Deep copy - you have to do this yourself
edit: This is one of the advantages of C++, you are free to decide how copying works. It might be that a copy of the object which only does read access to the memory can avoid the cost of copying the memory. You can also implement classes which only make a copy of the original data if the new object needs to do a write.
First off, the pointer in your class is static, (i.e. the compiler knows that there is a pointer in this class and what size it is, so no dynamic memory allocation is needed when the class is instantiated).
If you copy the class (and have not special copy constructor defined) then the pointer in the new class will point to the same place in memory as the pointer in the old class. To clarify:
#include <iostream>
class A {
public:
int *p;
};
int main() {
A a,b;
a.p = new int(10);
b = a;
std::cout << "*(a.p) = " << *(a.p) << std::endl; // 10
std::cout << "*(b.p) = " << *(b.p) << std::endl; // 10
*(b.p) = 3;
std::cout << "*(a.p) = " << *(a.p) << std::endl; // 3
std::cout << "*(b.p) = " << *(b.p) << std::endl; // 3
return 0;
}
Now if you want to allocate new memory when copying, you need to write a copy constructor and a copy assignment constructor:
#include <iostream>
class A {
public:
int *p;
A() : p(0) {}
A(const A& other) { // copy constructor
p = new int(*other.p);
}
A& operator=(const A& other) { // copy assignment constructor
// protect against self assignment
if (this != &other) {
if (p != 0) {
*p = *other.p;
} else { // p is null - no memory allocated yet
p = new int(*other.p);
}
}
return *this;
}
~A() { // destructor
delete p;
}
};
int main() {
A a,b;
a.p = new int(10);
b = a;
std::cout << "*(a.p) = " << *(a.p) << std::endl; // 10
std::cout << "*(b.p) = " << *(b.p) << std::endl; // 10
*(b.p) = 3;
std::cout << "*(a.p) = " << *(a.p) << std::endl; // 10
std::cout << "*(b.p) = " << *(b.p) << std::endl; // 3
return 0;
}
When you do this you should also write a destructor (see Rule of three ), because the memory that is being allocated in the copy / copy assignment constructors needs to be de-allocated if the class is destroyed:
Pointers don't instantiate dynamic memory allocation. Pointers and allocations are entirely different things.
If you copy a pointer that points to dynamically allocated memory, you have two pointers pointing at the same allocated memory. Since you've copied it, it already points to the memory block. Specifically, if using the compiler-generated copy constructor, the new pointer will point to the exact same thing as the old pointer. You don't need to do anything with it if that's OK.
You do have the problem of when to free the memory. Freeing it twice will typically cause heap corruption, which is nasty. Not freeing it will cause a memory leak, which may be acceptable in some circumstances. Freeing it before the other pointer is through with it will also cause problems. For this reason, people who have multiple pointers to the same memory often go to the Boost project and use their shared_ptr template (which will be in the upcoming new Standard, and is present in most up-to-date systems).
If you want each pointer to point to separate chunks of memory, you have to set that up by writing your own copy constructor, and allocating a new chunk and copying necessary data in it. (You also need to write your own assignment operator, for the exact same reasons, and your own destructor so you can free the memory. There's a rule of thumb, called the Rule of Three, that says if you need to write your own copy constructor, assignment operator, or destructor, you probably need to write all of them.)
The copied pointer will point to the exact same address. There's no new allocation.
Yes, pointers simply contain memory addresses, if you want to make a deeper copy you need to code that yourself, in the copy constructor.
If you're always referencing the same kind of data via a pointer from the same class, and need to copy the data along with the objects, you could also consider making it just a plain member, not a pointer.