The idea behind this is mainly educational but I might even consider using it in reality if turns out to be good. Here's my first try at implementing smart pointers:
template<typename T>
class smart_pointer{
T* pointer;
std::size_t *refs;
void clear(){
if (!--*refs){
delete pointer;
delete refs;
}
}
public:
smart_pointer(T* p = NULL)
: pointer(p), refs(new std::size_t(1))
{}
smart_pointer(const smart_pointer<T>& other)
: pointer(other.pointer), refs(other.refs)
{
++*refs;
}
~smart_pointer(){
clear();
}
smart_pointer<T>& operator=(const smart_pointer<T>& other){
if (this != &other){
clear();
pointer = other.pointer;
refs = other.refs;
++*refs;
}
return *this;
}
smart_pointer<T>& operator=(T* p){
if (pointer != p){
pointer = p;
*refs = 1;
}
return *this;
}
T& operator*(){
return *pointer;
}
const T& operator*() const{
return *pointer;
}
T* operator->(){
return pointer;
}
const T* operator->() const{
return pointer;
}
std::size_t getCounts(){
return *refs;
}
};
I have tested this under valrind and its clean. Also, to see how much the "smartness" makes things slower, I did the following test:
struct foo{
int a;
};
template<typename pointer_t>
class bar{
pointer_t f_;
public:
bar(foo *f)
:f_(f)
{}
void set(int a){
f_->a = a;
}
};
int main()
{
foo *f = new foo;
typedef smart_pointer<foo> ptr_t;
// typedef boost::shared_ptr<foo> ptr_t;
// typedef foo* ptr_t;
bar<ptr_t> b(f);
for (unsigned int i = 0; i<300000000; ++i)
b.set(i);
// delete f;
return 0;
}
Here is some timing between my implementation, boost, and raw pointers: (code compiled with clang++ -O3
)
typedef smart_pointer<foo> ptr_t;
real 0m0.006s
user 0m0.001s
sys 0m0.002s
typedef boost::shared_ptr<foo> ptr_t;
real 0m0.336s
user 0m0.332s
sys 0m0.002s
typedef foo* ptr_t;
real 0m0.006s
user 0m0.002s
sys 0m0.003s
My implementation seems to be running almost as fast as raw pointers, which I think is a good sign. What worries me here is why Boost is running slower. Have I missed something in my implementation that is important and I might get into trouble for later?
-
\$\begingroup\$ I have a question regarding incrementing the count. what if the smart pointer sp was originally pointed to something else, and you do a sp=sp1. In this case you need to decrease the old count first. \$\endgroup\$P Li– P Li2015年03月27日 07:58:48 +00:00Commented Mar 27, 2015 at 7:58
1 Answer 1
So the idea behind this is mainly educational...
Cool. Always good to try and understand how things work under the covers.
...but I might even consider using it in reality if turns out to be good.
Please rethink this. Smart pointer implementations are incredibly difficult to get right. Scott Myers, of Effective C++ fame, famously tried to implement a shared_ptr
. After feedback from something like 10 iterations, it was still wrong.
Let's go through a few things here.
smart_pointer(T* p = NULL)
: pointer(p), refs(new std::size_t(1))
This constructor should be explicit
to avoid implicit type conversions through construction.
Your operator=(T* p)
leaks. Here's a small example:
int main()
{
int *x = new int(5);
{
smart_pointer<int> sp(x);
sp = new int(6);
} // sp destroyed here
std::cout << *x << "\n"; // This should have been deleted but wasn't
}
To convince yourself that this is the case, modify your clear()
method:
void clear(){
if (!--*refs){
std::cout << "deleteting " << *pointer << " at " << pointer << "\n";
delete pointer;
delete refs;
}
}
This will print out "deleting 6 at <some memory address>
".
Here's just a small rundown of what boost::shared_ptr
(or std::shared_ptr
) provides that is missing here:
- Constructors allowing a
Deleter
, which is used instead of a rawdelete
. - Constructor allowing construction from
<template Tp1> shared_ptr(Tp1 * ...)
whereTp1
is convertible to typeT
. - Copy constructor allowing construction from a
shared_ptr
with convertible template type<T1>
. - Copy constructor taking
weak_ptr
,unique_ptr
. - Move constructor and assignment operator.
- Usage of
nothrow
where it is required to be. reset
, which will replace the currently managed object.- All the non-member operators like
operator<
,operator==
, etc that allow containers and algorithms to work correctly. For example,std::set
will not work correctly with your pointer class, neither will things likestd::sort
. - A specialization of
std::swap
. - A specialization of
std::hash
. Your pointer class won't work correctly withstd::unordered_set
orstd::unordered_map
. - explicit operator bool conversion for
nullptr
tests, so that one can writeif (<some_shared_ptr>) { ... }
. - Utility functions like
std::make_shared
which you should pretty much always use when creating ashared_ptr
.
The big pain point, and the reason why boost::shared_ptr
or std::shared_ptr
will be slower is the fact that they are thread-safe. Your smart_ptr
when accessed by multiple threads could very easily delete the pointer it holds before another class is done with it, leading to dereferencing of a delete
d object, and thus undefined behaviour. Furthermore, all of the atomic
operations are specialized for shared_ptr
.
The above is not an exhaustive list.
I don't write this to discourage you, but merely to try and reinforce the fact that writing something like this is a lot of work. It is very technical, and really, really easy to get wrong. In terms of the standard library, shared_ptr
may be one of the most difficult things to write correctly.
-
1\$\begingroup\$ I think we should hold an "everyone write a shared pointer day". \$\endgroup\$user1095108– user10951082013年09月05日 09:05:38 +00:00Commented Sep 5, 2013 at 9:05
-
5\$\begingroup\$ I would like to add that OP's implementation allocates the reference counter on the heap separately from the subject data. This can (if used very frequently) be a source of memory fragmentation, it is generally good to avoid small heap allocations not to mention the overhead of allocating 4 bytes is like 32 bytes or something. Also if the subject data and reference counter would be allocated together in a struct one would get better data locality and improved cache behavior. These two reasons are the reasons why std::make_shared<>() exists and is preferred for creation of smart pointers. \$\endgroup\$Emily L.– Emily L.2014年02月03日 17:10:43 +00:00Commented Feb 3, 2014 at 17:10
Explore related questions
See similar questions with these tags.