This question already has an answer here:
Why is the move-constructor not called?
(1 answer)
Closed 4 years ago.
I have written the below piece of code:
#define LOG cout << __PRETTY_FUNCTION__ << endl;
class MyClass
{
private:
int* ptr;
public:
MyClass()
: ptr(new int(10))
{
LOG
}
~MyClass()
{
LOG
if (ptr)
{
delete ptr;
ptr = nullptr;
}
}
MyClass(const MyClass& a)
: ptr(nullptr)
{
LOG
ptr = new int;
*ptr = *(a.ptr);
}
MyClass& operator=(const MyClass& a)
{
LOG
if (this == &a)
{
return *this;
}
delete ptr;
ptr = new int;
*ptr = *(a.ptr);
return *this;
}
MyClass(MyClass&& a)
: ptr(nullptr)
{
LOG
ptr = a.ptr;
a.ptr = nullptr;
}
MyClass& operator=(MyClass&& a)
{
LOG
if (this == &a)
{
return *this;
}
delete ptr;
ptr = a.ptr;
a.ptr = nullptr;
return *this;
}
void printClass()
{
LOG;
}
};
MyClass function()
{
MyClass m;
return m;
}
int main()
{
MyClass m = function();
return 0;
}
Output of the program:
MyClass::MyClass()
MyClass::~MyClass()
This does not call the move constructor. Is there something is wrong?
I was expecting the below output:
MyClass::MyClass()
MyClass::MyClass(MyClass&&)
MyClass::~MyClass()
MyClass::MyClass(MyClass&&)
MyClass::~MyClass()
MyClass::~MyClass()
It might look like the compiler is doing some optimisation. If this the case then why we need move constructor or move assignment operator for our case.
This does not call the move constructor. Is there something is wrong?
No, there is nothing wrong.
It might look like the compiler is doing some optimisation.
That's exactly what the compiler did.
If this the case then why we need move constructor or move assignment operator for our case.
Your class needs a custom move constructor and assignment operator, since the implicitly generated ones would not handle the allocated resource correctly.
Just because the compiler might optimise, is not a good reason to leave them out. Especially because in some other program, the move can not be optimized away at all.
PS.
if (ptr) in the destructor is redundant. It's fine to delete nullptr. You don't make the check in operator= either, which is fine.
deleteMe is a dangerous function. Deleting self might be useful in some very obscure cases, but your class doesn't show any need for it. The behaviour of calling this function on a non-dynamic instance would be undefined.
Initialising ptr to null in the move and copy constructor is redundant, since you overwrite the value unconditionally in the body of the constructor.
Related
I'm trying to make a sample implementation of std::unique_ptr. Here is the code:
include <iostream>
template<typename T>
class UniquePtr
{
public:
explicit UniquePtr(T * ptr)
:m_ptr(ptr)
{};
UniquePtr(UniquePtr const & other) = delete;
UniquePtr& operator=(UniquePtr const & other) = delete;
explicit UniquePtr(UniquePtr && other)
{
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
UniquePtr & operator=(UniquePtr && other)
{
std::cout << "Move assignment called " << std::endl;
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
~UniquePtr()
{
delete m_ptr;
}
T& operator*()
{
return *m_ptr;
}
T& operator->()
{
return m_ptr;
}
private:
T * m_ptr = nullptr;
};
int main()
{
UniquePtr<int> t(new int(3));
t= UniquePtr<int>(new int(4));
std::cout << *t << std::endl;
}
This code compiles and I'm able to see the value 4 in the output even after deleting the default assignment and copy constructor. What am I doing wrong?
In the assignment, move is called because the UniquePtr<int>(new int(4)) is constructing a temporary object, and in this case the compiler tries to use the move assignment if possible, else it would fall back to the copy assignment, which is deleted.
UniquePtr<int> t(new int(3));
t= UniquePtr<int>(new int(4)); // move assignment because temporary
auto k = t; // copy assignment since t is not temporary and so does not compile.
As commented, the move assignment is not returning *this, you should enable all warnings. Also the last operator-> has a syntax error, it returns a pointer but expects a reference.
Additionally, your code has a major issue, in case of exceptions you could have a memory leak. Suppose you write something like:
class Banana
{ ... }
void eatBanana(UniquePtr<Banana> banana, int amountToEat);
int computeAmount();
eatBanana(UniquePtr<Banana>(new Banana(3)), computeAmount());
If, for any reason, the computeAmount() function throws an exception, then the memory allocated by new could never be released, because computeAmount() could be executed between new and the constructor of the UniquePtr. For this reason, we normally use std::make_unique().
You should implement your own version of make_unique() and use it, it's trival, see here: https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
Here more information about the issues and the solution:
https://www.oreilly.com/library/view/effective-modern-c/9781491908419/ch04.html
I study for an exam in c++
and I was asked if in this code the d'tor of the class should use delete[] instead delete:
template <class T>
class ClonePtr
{
private:
T* ptr;
public:
explicit ClonePtr(T* p = nullptr) : ptr(p) {}
~ClonePtr() { if(ptr!=nullptr) delete []ptr; }
ClonePtr(const ClonePtr& other) : ptr(nullptr)
{
*this = other;
}
ClonePtr(ClonePtr&& other) : ptr(other.ptr)
{
other.ptr = nullptr;
}
ClonePtr& operator=(const ClonePtr& other)
{
if (this != &other)
{
delete ptr;
if (other.ptr != nullptr)
ptr = other.ptr->clone();
}
return *this;
}
T& operator*() const { return *ptr; }
};
The right answer to the question is yes,
but why is that?
+I have two more little question regarding this code:
It says in the exam that type T must be a class and cannot be a primitve type.
Why is that?
On the other hand, I tried writing this code:
class A
{
private:
int x;
public:
A(int x = 8) : x(x) { };
};
int main()
{
ClonePtr<int> h(new A(7));
}
and got a compiler error: C2664 "cannot convert argument 1 from A* to T*"
I will very much appreciate your help.
thanks :)
There is no way for us to answer this definitely one way or the other, because we can't see what kind of pointer is being passed to ClonePtr(T*), or what kind of pointer is being returned by clone(). This class is not doing any memory allocations of its own.
IF both pointers are being allocated with new and not by new[], then delete would be the correct answer, not delete[].
IF both pointers are being allocated with new[] and not by new, then delete[] would be the correct answer, not delete.
For that matter, since this code is taking ownership of memory allocated by something else, and it is clear by this class' use of nullptr and a move constructor (but not a move assignment operator!) that you are using C++11 or later, so you should be utilizing proper ownership semantics via std::unique_ptr or std::shared_ptr, not using a raw pointer at all.
Especially since your copy assignment operator is not setting ptr to nullptr after delete ptr; if other.ptr is nullptr, leaving *this in a bad state. Using proper ownership semantics would have avoided that.
Try this instead:
UPDATE: now you have posted additional code (that doesn't compile, since an A* can't be assigned to an int*, and A does not implement clone()) showing ptr being set to an object allocated with new, so the correct answer is:
delete MUST be used, not delete[].
So the "right answer" to your exam is wrong.
But proper ownership semantics would be even better, eg:
#include <memory>
template <class T>
class ClonePtr
{
private:
std::unique_ptr<T> ptr;
public:
ClonePtr(std::unique_ptr<T> p) : ptr(std::move(p)) {}
ClonePtr& operator=(ClonePtr other)
{
ptr = std::move(other.ptr);
return *this;
}
T& operator*() const { return *ptr; }
};
class A
{
private:
int x;
public:
A(int x = 8) : x(x) { };
std::unique_ptr<A> clone() const
{
return std::make_unique<A>(x);
// or: prior to C++14:
// return std::unique_ptr<A>(new A(x));
}
};
int main()
{
ClonePtr<A> h(std::make_unique<A>(7));
// or, prior to C++14:
// ClonePtr<A> h(std::unique_ptr<A>(new A(7)));
}
I am trying to implement an operator= in C++ for an object which has as a member a pointer to a user defined type which also has dynamic memory allocated in it.
So given the code below, how would on implement a correct operator= for B? What I am after is how is the dynamic memory in A copied to the new B object?
Any help would be much appreciated.
Thank you
class A
{
int a;
int* b;
public:
A()
{
a = 1;
b = new int [20];
}
};
class B
{
A* a;
public:
B()
{
a = new A;
}
}
For starters you should define at least copy constructor, copy assignment operator and destructor for the class A. Then it is simple to define the copy assignment operator for the class B.
For example
#include <iostream>
#include <algorithm>
class A
{
static const size_t N = 20;
int a;
int* b;
public:
A()
{
a = 1;
b = new int [N]();
}
A( const A &a ) : a( a.a ), b( new int [N] )
{
std::copy( a.b, a.b + N, this->b );
}
A & operator =( const A &a )
{
if ( &a != this )
{
this->a = a.a;
int *p = new int[N];
std::copy( a.b, a.b + N, p );
delete [] this->b;
this->b = p;
}
return *this;
}
~A()
{
delete []b;
}
};
class B
{
A* a;
public:
B() : a( new A )
{
}
// copy constructor
~B()
{
delete a;
}
B & operator =( const B &b )
{
if ( this != &b )
{
*this->a = *b.a;
}
return *this;
}
};
int main()
{
B b1;
B b2;
b1 = b2;
}
Pay attention to that in the copy assignment operator at first created a new array before deleting the old one. This allows to keep the stable state of the assignable object if an exception will occur.
At very first: Have a look at the rule of three, it is an absolute must go in given case. Consider, too, the rule of five, while not mandatory, you'll leave out a great optimisation opportunity...
The destructor would now delete[] the array (leaving this part to you...), a copy constructor would then do exactly that: (deep) copy the data:
A::A(A const& other)
: a(other.a), b(new int[20]) // assuming you have a fixed size for those arrays;
// better: introduce a constant for to avoid magic
// numbers in code!
{
// you created a new array, but yet need to fill it with the others value
std::copy(other.b, other.b + 20, b);
}
OK, first step. Using the copy and swap idiom, the assignment operator gets pretty simple:
A& operator=(A other) // YES, no reference! This will invoke the copy (or move!)
// constructor of your class!
{
swap(*this, other); // you'll need to implement it yet!
return *this;
// at this point, the destructor of other will clean up data that was potentially
// contained in *this before...
}
Finally the move constructor:
A::A(A&& other)
: a(0), b(nullptr)
{
swap(*this, other);
// again swapping??? well, sure, you want the data from other to be contained
// in *this, and we want to leave other in some kind of valid state, which the
// nullptr is fine for (it's fine to delete[] a null pointer, so you don't even
// need to check in the destructor...)
}
And now up to you: class B analogously...
Side note: you get away a bit cheaper by use of a smart pointer (std::unique_ptr), it will allow you to default destructor and move constructor + assignment operator, solely copy constructor and assignment operator need to be implemented explicitly (std::unique_ptr is not copiable...).
There is a class called "X" like this:
class X {
private:
int *ptr;
public:
X() {
ptr = new int[2];
ptr[0] = 0;
ptr[1] = 0;
}
X(int a, int b) {
ptr = new int[2];
ptr[0] = a;
ptr[1] = b;
}
X(const X &val) {
delete[] ptr;
ptr = new int[2];
ptr[0] = val.ptr[0];
ptr[1] = val.ptr[1];
}
X get() {
X ret(ptr[0], ptr[1]);
return ret;
}
};
Suppose that there are variable X that is defined with X v(2, 3).
If we call v.get(), it's okay.
But, if we call v.get().get(), it's not okay. It produces runtime error in delete[] ptr section in copy constructor, which the content is that I am trying to delete the undefined (0xcccccccc) pointer.
One possible option for dealing with this is, that using C++-STL like <array> or <vector>. But I want to implement with pointer.
How to deal with this runtime error, with pointer-based implementation?
The simple answer is that you shouldn't delete[] ptr in the copy constructor, because it is uninitialised. You don't need to delete[] ptr in assignment, either. The only place delete or delete[] should occur is in a destructor.
Tidying up your class, without changing from owning raw pointers
class X {
private:
int *ptr;
public:
X() : X(0, 0) { }
X(int a, int b) : ptr(new int[2]) {
ptr[0] = a;
ptr[1] = b;
}
X(const X &val) : X(val.ptr[0], val.ptr[1]) { }
X& operator=(const X &val)
{
ptr[0] = val.ptr[0];
ptr[1] = val.ptr[1];
return *this;
}
X(X&& val) : ptr(val.ptr) {
val.ptr = nullptr;
}
X& operator=(X&& val) {
std::swap(ptr, val.ptr);
return *this;
}
~X() { delete[] ptr; }
X get() {
return *this;
}
};
As a point of nomenclature, a constructor is only a "copy constructor" if it takes an instance of the class as it's single (non-default) parameter. X::X(int, int) is just a constructor. That means there are at most 4 copy constructors (otherwise they would be ambiguous under overload resolution)
X::X(X &)
X::X(const X &)
X::X(volatile X &)
X::X(const volatile X &)
However defining more than one is a terrible idea, unless you in an obfuscation contest
I have a class like the following:
class A {
SuperHugeClass* s;
public:
A(){ s = new SuperHugeClass(); }
};
Because SuperHugeClass takes a lot of memory, I'm fine with the shallow copying provided by the default constructor and assignment operator. However, I also don't want to leak memory, so I need to delete s, but I have to be careful about it because otherwise I'll delete it more than once.
One way of doing this is by refcounting s as follows:
class A {
int* refcount;
SuperHugeClass* s;
public:
A(){
refcount = new int(1);
s = new SuperHugeClass();
}
A(const A& other) : refcount(other.refcount), s(other.s) {
(*refcount)++;
}
~A() {
(*refcount)--;
if (!(*refcount)) {
delete refcount;
delete s;
}
}
friend void swap(const A& a, const A& aa) {
std::swap(a.refcount, aa.refcount);
std::swap(a.s, aa.s);
}
A& operator=(A other) {
swap(*this, other);
return (*this);
}
};
This is the first time I've needed to do something like this, but it seems to me that this should be pretty standard and so there should be a 'canonical' solution. Are there any other ways of doing this? Thanks!
Use std::shared_ptr
class A {
std::shared_ptr<SuperHugeClass> s;
public:
A()
: s(new SuperHugeClass())
{
}
};
and thats it. Default generated copy constructor/assignment operator/destructor do just what you need.
Use std/boost::shared_ptr instead of your ref-counted pointer.