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
Related
I wanted to know what exactly happens internally when we call std::move on an object.
For example:
class Holder {
int* m_ptr;
int m_size;
public:
Holder(int size) : m_size(size),
m_ptr(size ? new int[size] : 0) {
cout << "Constructor\n";
}
~Holder() {
cout << "Destructor\n";
delete[] m_ptr;
}
Holder(const Holder& other) {
cout << "Copy Constructor\n";
m_ptr = new int[other.m_size];
std::copy(other.m_ptr, other.m_ptr + other.m_size, m_ptr);
m_size = other.m_size;
}
void swap(Holder& other) noexcept {
std::swap(this->m_ptr, other.m_ptr);
std::swap(this->m_size, other.m_size);
}
Holder& operator=(const Holder& other) {
cout << "Copy Assignment\n";
Holder(other).swap(*this);
}
Holder(Holder&& other) : m_ptr(other.m_ptr), m_size(other.m_size) {
cout << "Move Constructor\n";
other.m_ptr = nullptr;
other.m_size = 0;
}
Holder& operator=(Holder&& that) noexcept {
cout << "Move Assignment\n";
Holder(std::move(that)).swap(*this);
return *this;
}
};
Holder createHolder(int size) {
return Holder(size);
}
int main(void) {
Holder h = createHolder(1000);
return 0;
}
Gives the output as when compiled with '-fno-elide-constructors':
Constructor
Move Constructor
Destructor
Move Constructor
Destructor
Destructor
Since we are returning from a function createHolder and the return object is on the stack, how does the main function still receives it? Not getting exactly what happens internally in memory.
Thanks.
std::move is a noop function that converts any kind of reference into an rvalue reference. So it doesn't actually "do" anything, it just changes how it's argument is used.
template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;
Returns: static_cast<remove_reference_t<T>&&>(t).
std::move make your operator =, constructor is call correctly with what you defined in the Holder class
Holder(Holder&& other) : m_ptr(other.m_ptr), m_size(other.m_size) {
cout << "Move Constructor\n";
other.m_ptr = nullptr;
other.m_size = 0;
}
Holder& operator=(Holder&& that) noexcept {
cout << "Move Assignment\n";
Holder(std::move(that)).swap(*this);
return *this;
}
Since we are returning from a function createHolder and the return object is on the stack, how does the main function still receives it?
because your move constructor
you already copy pointer m_ptr(other.m_ptr) m_size(other.m_size) from
Holder(size) -> move to Holder object in return register on stack -> move to Holder h in main function
Constructor -> of Holder(size);
Move Constructor -> Holder(size) move to Holder object in return register on stack
Destructor -> destructor of Holder(size)
Move Constructor -> from Holder object on stack -> Holder h in main
Destructor -> destructor of Holder object in return register
Destructor -> destructor of Holder h in main
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'm performing some tests about move semantics, and my class behavior seems weird for me.
Given the mock class VecOfInt:
class VecOfInt {
public:
VecOfInt(size_t num) : m_size(num), m_data(new int[m_size]) {}
~VecOfInt() { delete[] m_data; }
VecOfInt(VecOfInt const& other) : m_size(other.m_size), m_data(new int[m_size]) {
std::cout << "copy..." <<std::endl;
std::copy(other.m_data, other.m_data + m_size, m_data);
}
VecOfInt(VecOfInt&& other) : m_size(other.m_size) {
std::cout << "move..." << std::endl;
m_data = other.m_data;
other.m_data = nullptr;
}
VecOfInt& operator=(VecOfInt const& other) {
std::cout << "copy assignment..." << std::endl;
m_size = other.m_size;
delete m_data;
m_data = nullptr;
m_data = new int[m_size];
m_data = other.m_data;
return *this;
}
VecOfInt& operator=(VecOfInt&& other) {
std::cout << "move assignment..." << std::endl;
m_size = other.m_size;
m_data = other.m_data;
other.m_data = nullptr;
return *this;
}
private:
size_t m_size;
int* m_data;
};
OK CASE
When I insert a single value in-place:
int main() {
std::vector<VecOfInt> v;
v.push_back(10);
return 0;
}
Then it gives me the following output (what I think is fine):
move...
WEIRD CASE
When I insert three different values in-place:
int main() {
std::vector<VecOfInt> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
return 0;
}
Then the output calls the copy constructor 3 times:
move...
move...
copy...
move...
copy...
copy...
What I'm missing here?
Move construction and move assignment aren't used by std::vector when reallocating unless they are noexcept or if there is no copying alternatives. Here is your example with noexcept added :
class VecOfInt {
public:
VecOfInt(size_t num) : m_size(num), m_data(new int[m_size]) {}
~VecOfInt() { delete[] m_data; }
VecOfInt(VecOfInt const& other) : m_size(other.m_size), m_data(new int[m_size]) {
std::cout << "copy..." <<std::endl;
std::copy(other.m_data, other.m_data + m_size, m_data);
}
VecOfInt(VecOfInt&& other) noexcept : m_size(other.m_size) {
std::cout << "move..." << std::endl;
m_data = other.m_data;
other.m_data = nullptr;
}
VecOfInt& operator=(VecOfInt const& other) {
std::cout << "copy assignment..." << std::endl;
m_size = other.m_size;
delete m_data;
m_data = nullptr;
m_data = new int[m_size];
m_data = other.m_data;
return *this;
}
VecOfInt& operator=(VecOfInt&& other) noexcept {
std::cout << "move assignment..." << std::endl;
m_size = other.m_size;
m_data = other.m_data;
other.m_data = nullptr;
return *this;
}
private:
size_t m_size;
int* m_data;
};
A live live example outputs :
move...
move...
move...
move...
move...
move...
This is done to keep exception safety. When resizing a std::vector fails, it will try to leave the vector as it was before the attempt. But if a move operation throws half way through reallocation, there is no safe way to undo the moves that have already been made successfully. They very well may also throw. The safest solution is to copy if moving might throw.
std::vector allocates a block of contiguous memory for its elements. When the allocated memory is too short for storing new elements, a new block is allocated and all current elements are copied from the old block to the new block.
You can use std::vector::reserve() to pre-size the capacity of the std::vector memory before adding new elements.
Try the following:
int main() {
std::vector<VecOfInt> v;
v.reserve(3);
v.push_back(10);
v.push_back(20);
v.push_back(30);
return 0;
}
and you will get:
move...
move...
move...
But to get move constructor being called even when reallocating you should make it noexcept like:
VecOfInt(VecOfInt&& other) noexcept {...}
tl;dr: std::vector will copy instead of moving if your move constructor isn't noexcept.
1. It's not about the members and the dynamic allocations
The issue is not with what you do with the fields of foo. So your source could be just:
class foo {
public:
foo(size_t num) {}
~foo() = default
foo(foo const& other) {
std::cout << "copy..." <<std::endl;
}
foo(foo&& other) {
std::cout << "move..." << std::endl;
}
foo& operator=(foo const& other) {
std::cout << "copy assignment..." << std::endl;
return *this;
}
foo& operator=(foo&& other) {
std::cout << "move assignment..." << std::endl;
return *this;
}
};
and you still get the same behavior: try it.
2. The move's you do see are a distraction
Now, push_back() will first construct an element - foo in this case; then make sure there's space for it in the vector; then std::move() it into its place. So 3 of your moves are of that kind. Let's try using emplace_back() instead, which constructs the vector element in its place:
#include <vector>
#include <iostream>
struct foo { // same as above */ };
int main() {
std::vector<foo> v;
v.emplace_back(10);
v.emplace_back(20);
v.emplace_back(30);
return 0;
}
This gives us:
copy
copy
copy
try it. So the moves were just a distraction really.
3. The copies are due to the vector resizing itself
Your std::vector gradually grows as you insert elements - necessitating either moves or copy constructions. See #NutCracker's post for details.
4. The real issue is exceptions
See this question:
How to enforce move semantics when a vector grows?
std::vector doesn't know it can safely move elements when resizing - where "safely" means "without exceptions", so it falls back to copying.
5. "But my copy ctor can throw an exception too!"
I guess the rationale is that if you get an exception while copying the smaller buffer - you still haven't touched it, so at least your original, non-resized vector is valid and can be used. If you started moving elements and got an exception - then you have no valid copy of the element anyway, not to speak of a valid smaller vector.
Your move constructor does not have the specifier noexcept.
Declare it like
VecOfInt(VecOfInt&& other) noexcept : m_size(other.m_size) {
std::cout << "move..." << std::endl;
m_data = other.m_data;
other.m_data = nullptr;
}
Otherwise the class template std::vector will call the copy constructor.
Add noexcept decorators to your move constructor and move assignment operator:
VecOfInt(VecOfInt&& other) noexcept : m_size(other.m_size) {
std::cout << "move..." << std::endl;
m_data = other.m_data;
other.m_data = nullptr;
}
VecOfInt& operator=(VecOfInt&& other) noexcept {
std::cout << "move assignment..." << std::endl;
m_size = other.m_size;
m_data = other.m_data;
other.m_data = nullptr;
return *this;
}
Some functions (for instance, std::move_if_noexcept, used by std::vector) will decide to copy your object if its move operations are not decorated with noexcept., i.e. there is no guarantee they will not throw. That's why you should aim to make your move operations (move constructor, move assignment operator) noexcept. This can significantly improve the performance of your program.
According to Scott Meyer's in Effective Modern C++:
std::vector takes advantage of this "move if you can, but copy if
you must" strategy , and it's not the only function in the Standard
Library that does. Other functions sporting strong exception safety
guarantee in C++98 (e.g. std::vector::reserve, std::deque::insert,
etc) behave the same way. All these functions replace calls to copy
operations in c++98 with calls to move operations in C++11 only if the
move operations are known not to emit exceptions. But how can a
function know if a move operation won't produce an exception? The
answer is obvious: it checks to see if the operation is declared
noexcept.
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.
despite the ocean of smart pointer questions out there, I seem to be stuck with one more. I am trying to implement a ref counted smart pointer, but when I try it in the following case, the ref count is wrong. The comments are what I think should be the correct ref counts.
Sptr<B> bp1(new B); // obj1: ref count = 1
Sptr<B> bp2 = bp1; // obj1: ref count = 2
bp2 = new B; // obj1: ref count = 1, obj2: rec count = 1 **problem**
In my implementation, my obj2 ref count is 2, because of this code:
protected:
void retain() {
++(*_rc);
std::cout << "retained, rc: " << *_rc << std::endl;
}
void release() {
--(*_rc);
std::cout << "released, rc: " << *_rc << std::endl;
if (*_rc == 0) {
std::cout << "rc = 0, deleting obj" << std::endl;
delete _ptr;
_ptr = 0;
delete _rc;
_rc = 0;
}
}
private:
T *_ptr;
int *_rc;
// Delegate private copy constructor
template <typename U>
Sptr(const Sptr<U> *p) : _ptr(p->get()), _rc(p->rc()) {
if (p->get() != 0) retain();
}
// Delegate private assignment operator
template <typename U>
Sptr<T> &operator=(const Sptr<U> *p) {
if (_ptr != 0) release();
_ptr = p->get();
_rc = p->rc();
if (_ptr != 0) retain();
return *this;
}
public:
Sptr() : _ptr(0) {}
template <typename U>
Sptr(U *p) : _ptr(p) { _rc = new int(1); }
// Normal and template copy constructors both delegate to private
Sptr(const Sptr &o) : Sptr(&o) {
std::cout << "non-templated copy ctor" << std::endl;
}
template <typename U>
Sptr(const Sptr<U> &o) : Sptr(&o) {
std::cout << "templated copy ctor" << std::endl;
}
// Normal and template assignment operator
Sptr &operator=(const Sptr &o) {
std::cout << "non-templated assignment operator" << std::endl;
return operator=(&o);
}
template <typename U>
Sptr<T> &operator=(const Sptr<U> &o) {
std::cout << "templated assignment operator" << std::endl;
return operator=(&o);
}
// Assignment operator for assigning to void or 0
void operator=(int) {
if (_ptr != 0) release();
_ptr = 0;
_rc = 0;
}
The constructor is initialized with a ref count = 1, but in my assignment operator, I am retaining the object, making the ref count = 2. But it should only be 1 in this case, because bp2 = new B is only one pointer to that object. I've looked over various smart pointer implementation examples, and I can't seem to figure out how they deal with this case that I'm having trouble with.
Thanks for your time!
The assignment operator is riddled with small errors and unnecessary complex anyway. For example, when you assign Sptr<T> to itself it will have funny effects. Most manually written assignment operators should look like this:
T& T::operator= (T const& other) {
T(other).swap(*this);
return *this;
}
... or
T& T::operator= (T other) {
other.swap(*this);
return *this;
}
Once stateful allocators enter the game things change a bit but we can ignore this detail here. The main idea is to leverage the existing work done for the copy constructor and the destructor. Note, that this approach also works if the right hand side isn't T, i.e., you can still leverage a corresponding constructor, the destructor, and swap(). The only potentially additional work is desirable anyway, and trivial to implement: the swap() member. In your case that is very simple, too:
template <typename T>
void Sptr<T>::swap(Sptr<T>& other) {
std::swap(this->_ptr, other._ptr);
std::swap(this->_rc, other._rc);
}
Just a note on your assignment operator taking an int: This is a very bad idea! To reset the pointer, you should probably better have a reset() method. In C++ 2011 you could reasonably have a method taking a std::nullptr_t for this purpose, though.
Define an assignment operator for the raw pointer type. Otherwise, it will construct a new smart pointer with ref count 1, which you will then increase to 2.
Example:
template <typename U>
Sptr<T> &operator=(U *p)
{
if (_ptr != 0)
{
_ptr = p;
_rc = new int(1);
}
return *this;
}
This stuff is notoriously tricky, and there are probably more bugs waiting. I would make the constructor explicit to prevent other unintentional constructions.
From what I see of your code, you need a proper destructor in your smart pointer class to call release and fix the counter. You need at least that modification for your counter to work correctly.
I didn't see a Sptr(T *) constructor in your code, did you omit it?
As a side note, I would probably use a std::pair to store the counter and the T pointer, but your data layout works as it is. As another answer pointed it out, you should pay attention to the self assignment case though.
Here's a minimal implementation following your data layout and interface choices:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Sptr {
protected:
T *_ptr;
int *_rc;
virtual void retain() {
if (_rc) // pointing to something
++(*_rc);
clog << "retain : " << *_rc << endl;
}
virtual void release() {
if (_rc) {
--(*_rc);
clog << "release : " << *_rc << endl;
if (*_rc == 0) {
delete _ptr;
_ptr = NULL;
delete _rc;
_rc = NULL;
}
}
}
public:
Sptr() : _ptr(NULL), _rc(NULL) {} // no reference
virtual ~Sptr() { release(); } // drop the reference held by this
Sptr(T *p): _ptr(p) { // new independent pointer
_rc = new int(0);
retain();
}
virtual Sptr<T> &operator=(T *p) {
release();
_ptr = p;
_rc = new int(0);
retain();
return *this;
}
Sptr(Sptr<T> &o) : _ptr(o._ptr), _rc(o._rc) {
retain();
}
virtual Sptr<T> &operator=(Sptr<T> &o) {
if (_rc != o._rc){ // different shared pointer
release();
_ptr = o._ptr;
_rc = o._rc;
retain();
}
return *this;
}
};
int main(){
int *i = new int(5);
Sptr<int> sptr1(i);
Sptr<int> sptr2(i);
Sptr<int> sptr3;
sptr1 = sptr1;
sptr2 = sptr1;
sptr3 = sptr1;
}
_rc is the indicator in your class that a pointer is shared with another instance or not.
For instance, in this code:
B *b = new B;
Sptr<B> sptr1(b);
Sptr<B> sptr2(b);
sptr1 and sptr2 are not sharing the pointer, because assignments were done separately, and thus the _rc should be different, which is effectively the case in my small implementation.