For training purposes, I am trying to write my own smartpointer, imitating std::shared_ptr. I have a static std::map<void *, int> ref_track that keeps track whether there are still shared pointer referencing a certain block in memory.
My concept is this:
template <typename PType>
class shared_ptr
{
public:
shared_ptr()
: value_(nullptr), ptr_(nullptr)
{}
template <typename T>
explicit shared_ptr(T * ptr)
: shared_ptr()
{
reset(ptr);
}
template <typename T>
shared_ptr(shared_ptr<T> const & other)
: shared_ptr()
{
reset(other.get());
}
~shared_ptr()
{
reset();
}
void reset()
{
if(value_)
{
delete value_; // Segmentation fault here!
value_ = 0;
ptr_ = 0;
}
}
template <typename T>
void reset(T * ptr)
{
reset();
if(ptr)
{
value_ = new shared_ptr_internal::storage_impl<
T
>(ptr);
ptr_ = ptr;
}
}
PType * get() const
{
return ptr_;
}
typename shared_ptr_internal::ptr_trait<PType>::type operator *()
{
return *ptr_;
}
private:
shared_ptr_internal::storage_base * value_;
PType * ptr_;
};
When running my test suite, I noticed that
shared_ptr<int> a(new int(42));
a.reset(new int(13));
works fine, but
shared_ptr<int> a(new int(42));
a = shared_ptr<int>(new int(13));
leads to problems: *a is 0 instead of 13, and delete value_ crashes with a segmentation fault in the destructor of a. I have marked the crash in the source code with a comment.
The used internal classes are
namespace shared_ptr_internal
{
typedef std::map<void *, int> ref_tracker;
typedef std::map<void *, int>::iterator ref_tracker_iterator;
typedef std::pair<void *, int> ref_tracker_entry;
static ref_tracker ref_track;
struct storage_base
{
virtual ~storage_base() {}
};
template <typename PType>
struct storage_impl : storage_base
{
storage_impl(PType * ptr)
: ptr_(ptr)
{
ref_tracker_iterator pos = ref_track.find(ptr);
if(pos == ref_track.end())
{
ref_track.insert(
ref_tracker_entry(ptr, 1)
);
}
else
{
++pos->second;
}
}
~storage_impl()
{
ref_tracker_iterator pos = ref_track.find(ptr_);
if(pos->second == 1)
{
ref_track.erase(pos);
delete ptr_;
}
else
{
--pos->second;
}
}
private:
PType * ptr_;
};
template <typename PType>
struct ptr_trait
{
typedef PType & type;
};
template <>
struct ptr_trait<void>
{
typedef void type;
};
}
Sorry for the bulk of source code, but I really do not know how I could narrow it down further. I would be grateful for any ideas what could be causing the segfault, and moreover why this does not happen when using reset manually.
Update
My (not-working) assignment operator:
template <typename T>
shared_ptr<PType> & operator =(shared_ptr<T> const & other)
{
if(this != &other)
{
value_ = nullptr;
ptr_ = nullptr;
reset(other.get());
}
return *this;
}
You're missing an assignment operator.
This means that in the following code:
a = shared_ptr<int>(new int(13));
a temporary shared pointer is created; then the default assignment operator simply copies the pointer to a without releasing the old value or updating the reference count; then the temporary deletes the value, leaving a with a dangling pointer.
Seems like a violation of the rule of three: You have a custom copy constructor and a custom destructor, but no custom assignment operator. Therefore a = shared_ptr<int>(new int(13)) will just copy the two pointers value_ and ptr_ from the temporary, without any update of your reference tracking. Therefore when you destroy the temporary, there are no more tracked references to that pointer, which will lead to its deletion. Also note that the old pointer will have been leaked in the assignment.
you forgot to add an assignment operator to your pointer class that should decrement the number references to the old object and increment the number of references to the assigned object. Most times it's the easiest way to implement operator= in terms of a copy d'tor and a swap function:
shared_ptr& shared_ptr<T>::operator=( shared_ptr<T> other )
{
other.swap( this );
return *this;
}
Related
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 building an add-in for a program. My add-in manipulates Ptr objects passed to me by the host application. I would like to create a vector of externally created and managed objects by the host. Unfortunately, the documentation doesn't have any clear examples of how to do this.
class Players {
vector<Ptr<Player>> vectorOfGamers; // who deletes and when this?
public void CreatePlayers () {
// call static application to create three players
for ( int i = 0; i < 3; i++ )
vectorOfGamers.push_back(Application.GetNextPlayer());
}
}
Confused about how to build this class and prevent memory leaks and causing a null exception if items are deleted prematurely. Also, how do I use modern C++ facilities to achieve this yet gain as much of the benefits of the new memory management like make_shared, make_unique, nullptr, etc?
For your information, below is a snapshot of Ptr.I am confused about the Ptr as it appears superfluous given modern C++'s new memory management facilities.
class IncompleteType
{
public:
template<typename T> static void addref(void* ptr) { reinterpret_cast<adsk::core::ReferenceCounted*>(ptr)->addref(); }
template<typename T> static void release(void* ptr) { reinterpret_cast<adsk::core::ReferenceCounted*>(ptr)->release(); }
};
class CompleteType
{
public:
template<typename T> static void addref(T* ptr) { ptr->addref(); }
template<typename T> static void release(T* ptr) { ptr->release(); }
};
template<class T, class PT = IncompleteType>
class Ptr
{
public:
typedef T element_type;
Ptr() : ptr_(nullptr) {}
Ptr(const Ptr& rhs) : ptr_(nullptr) { reset(rhs.ptr_); }
Ptr(const T* ptr, bool attach = true) : ptr_(nullptr) { reset(ptr, attach); }
// casting constructor. call operator bool to verify if cast was successful
template<class V, class VPT>
Ptr(const Ptr<V, VPT>& rhs) : ptr_(nullptr) {
if (rhs)
reset(rhs->template query<T>(), false);
}
~Ptr() { reset(nullptr); }
void operator=(const Ptr<T, PT>& rhs) { if (&rhs != this) reset(rhs.ptr_); }
void operator=(const T* ptr) { reset(ptr, true); }
// casting assignment operator. call operator bool to verify if cast was successful
template<class V, class VPT>
void operator=(const Ptr<V, VPT>& rhs) {
if (rhs)
reset(rhs->template query<T>(), false);
else
reset(nullptr);
}
void reset(const T* ptr, bool attach = false) {
if (ptr_ != ptr)
{
if (ptr_)
PT::template release<T>(ptr_);
ptr_ = const_cast<T*>(ptr);
if (!attach && ptr_)
PT::template addref<T>(ptr_);
}
}
T* operator->() const { assert(ptr_ != nullptr); if (ptr_ == nullptr) throw std::exception(); return ptr_; }
// Test if this pointer is empty (if operator-> will throw)
/*explicit*/ operator bool() const { return ptr_ != nullptr; }
bool operator==(const Ptr& rhs) const { return ptr_ == rhs.ptr_; }
bool operator!=(const Ptr& rhs) const { return ptr_ != rhs.ptr_; }
bool operator<(const Ptr& rhs) const { return ptr_ < rhs.ptr_; }
// Iteration support. Only usable if T has count and item members and an iterable_type
typedef Iterator<T, PT> iterator;
iterator begin() const { return Iterator<T, PT>(*this); }
iterator end() const { return Iterator<T, PT>(*this, true); }
// Caution the following functions if used incorrectly can cause a reference count leak
T* get() const { return ptr_; }
T* detach() { T* t = ptr_; ptr_ = nullptr; return t; }
private:
T* ptr_;
};
You’re probably right that Ptr wouldn’t be needed in a post-C++03 environment. Probably your plugin API is old enough that C++11 wasn’t around back then. From the code you posted my best guess is that Ptr is supposed to be a reference counted smart pointer that manages shared ownership like std::shared_ptr does.
How exactly you use that thing should become clear from the plguin API documentation and maybe the source code of your host program. Just from the snippet you posted and without even mentioning the program’s name it’s impossible to say anything definite.
I am trying to implement a smart pointer (basically unique pointer) in C++ using templates for my understanding.
This is what I have coded
using namespace std;
template<typename T>
class smartPointer
{
private:
T *mPtr;
public:
smartPointer(T* init=nullptr):mPtr(init)
{
cout<<"Inside ctr"<<endl;
}
//silence the default ctr
smartPointer() = delete;
//disable copy ctr and copy assignment
smartPointer (const smartPointer& other) = delete;
smartPointer& operator=(const smartPointer& other) = delete;
//implement move ctr and move assignment
smartPointer (smartPointer&& other)
{
cout<<"Inside move ctr "<<endl;
mPtr = other.mPtr;
other.mPtr = nullptr;
}
smartPointer& operator=(smartPointer&& other)
{
cout<<"Inside move = before "<<endl;
if(&other != this)
{
mPtr = other.mPtr;
other.mPtr = nullptr;
cout<<"Inside move = after "<<endl;
}
return *this;
}
//deference
T& operator*()
{
return *mPtr;
}
//arrow access operator
T* operator->()
{
return mPtr;
}
~smartPointer()
{
cout<<"Inside ~dtr"<<endl;
if(mPtr != nullptr)
delete mPtr;
}
};
int main()
{
smartPointer<int> sptr(new int);
// Even smartPointer<int> sptr(new int[20]); too works
*sptr = 10;
cout<<"Value pointed by the pointer is "<<*sptr<<endl;
smartPointer<int> sptr2(new int);
// sptr2 = sptr; // complains as expected
sptr2 = move(sptr); // works well
/*
How to
smartPointer<int[]> sptr(new int[20]);
*/
return 0;
}
How to make this smart pointer to work for smartPointer ?
The build command I give is
g++ -std=c++11 -smartPointer.cpp -o smartPointer
You can add operator[] to the base template, and use SFINAE to ensure it only compiles when the type is an array type. You would do the same to make sure operaotr* and operaotr-> only compile for the non-array version. Then, tag dispatch can be used to call the correct delete expression.
template<typename T>
class smartPointer
{
private:
T *mPtr;
del_member(std::true_type) { delete[] mPtr; }
del_member(std::false_type) { delete mPtr; }
public:
// Removed you code for brevity.
//deference
auto operator*() -> std::enable_if_t<!std::is_array<T>::value, T&>
{
return *mPtr;
}
//arrow access operator
auto operator->() -> std::enable_if_t<!std::is_array<T>::value, T*>
{
return mPtr;
}
auto operator[](std::size_t idx) -> std::enable_if_t<std::is_array<T>::value, T&>
{
return mPtr[idx];
}
~smartPointer()
{
del_member(std::is_array<T>{});
}
};
The above is perfectly legal, and can exist in the same template. Remember the members of a template are instantiated only when used. So unless users of your class try to use an operator which the underlying pointer shouldn't support, they will get no errors.
If your compiler doesn't support c++14 type traits yet, the above trailing return types can be turned to the form:
typename std::enable_if<CONDITION, TYPE>::type
For your c'tor, I recommend you don't hard-code T*. Instead add the following type alias to your class, and change the c'tor accordingly.
using ptr_type = std::conditional_t<std::is_array<T>::value, std::decayt_t<T>, std::add_pointer_t<T>>;
smartPointer(ptr_type init)
This question is just for improvement purpose. There is a function below:
void Func_A(u8* arg) {
bool local_arg=false;
if(!arg) {
//allocate memory for arg
local_arg=true;
}
//process arg
I am calling this function from multiple places with NULL and non NULL argument.
I just want to ask whther there is a better way of handling such this without local_arg or not.
You can sometimes use...
u8* p = arg ? arg : new u8(...);
...do things with *p...
if (!arg) delete p;
A little fancier, you can write a smart pointer with runtime-configurable ownership:
template <typename T>
class Uncertain_Ownership_Ptr
{
public:
enum Ownership { Own_It, Dont_Own_It };
Uncertain_Ownership_Ptr(T* p, Ownership own) : p_(p), own_(own) { }
Uncertain_Ownership_Ptr(const Uncertain_Ownership_Ptr&) = delete;
void operator=(const Uncertain_Ownership_Ptr&) = delete;
~Uncertain_Ownership_Ptr() { if (own_ == Own_It) delete p_; }
T& operator*() { return *p_; }
const T& operator*() const { return *p_; }
T* operator->() { return p_; }
const T* operator->() const { return p_; }
private:
T* p_;
Ownership own_;
};
...then...
void Func_A(u8* arg)
{
Uncertain_Ownership_Ptr p(arg ? arg : new u8(...),
arg ? Uncertain_Ownership_Ptr::Dont_Own_It : Uncertain_Ownership_Ptr::Own_It);
// use *p ...
}
Why if I overload the -> operator in this code
class subobj
{
public:
void get()
{
printf("ea");
}
};
template<typename T> class testPT
{
public:
T* operator->()
{
return ptr;
}
T* ptr;
};
int main()
{
subobj myobj;
testPT<subobj> myclass;
myclass.ptr = &myobj;
myclass->get();
return 0;
}
I get the "ea" string printed?
By using "myclass->", that should just return a T*, a pointer to the object. I should have done something like
myclass->->get()
to actually call the get() routine. Where am I getting wrong?
operator-> is magic. :)
It uses chaining, that means it is called again as long as you don't return a plain pointer. When you return a plain pointer, it does one final call to operator->. When you call operator->
obj->foo;
it translates to:
(obj.operator->())->foo;
except when obj is a plain pointer.
You could even do this:
template<typename T> class testPT2
{
public:
T* operator->()
{
return ptr;
}
T* ptr;
};
template<typename T> class testPT
{
public:
testPT2<T> operator->()
{
testPT2<T> p2;
p2.ptr = ptr;
return p2;
}
T* ptr;
};
and it would still work by effectively applying operator-> three times.