patterncppMajor
Efficient smart pointer implementation in C++
Viewed 0 times
smartimplementationefficientpointer
Problem
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:
I have tested this under valrind and its clean. Also, to see how much the "smartness" makes things slower, I did the following test:
Here is some timing between my implementation, boost, and raw pointers: (code compiled with
```
typedef smart_pointer ptr_t;
r
template
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& other)
: pointer(other.pointer), refs(other.refs)
{
++*refs;
}
~smart_pointer(){
clear();
}
smart_pointer& operator=(const smart_pointer& other){
if (this != &other){
clear();
pointer = other.pointer;
refs = other.refs;
++*refs;
}
return *this;
}
smart_pointer& 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
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 ptr_t;
// typedef boost::shared_ptr ptr_t;
// typedef foo* ptr_t;
bar 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 ptr_t;
r
Solution
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
Let's go through a few things here.
This constructor should be
Your
To convince yourself that this is the case, modify your
This will print out "deleting 6 at `
The big pain point, and the reason why
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,
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 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 `
".
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 raw delete.
- Constructor allowing construction from
shared_ptr(Tp1 * ...) where Tp1 is convertible to type T.
- Copy constructor allowing construction from a
shared_ptr with convertible template type .
- 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- A specialization of
std::swap.
- A specialization of
std::hash. Your pointer class won't work correctly withstd::unordered_setorstd::unordered_map.
- explicit operator bool conversion for
nullptrtests, so that one can writeif () { ... }.
- Utility functions like
std::make_sharedwhich 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 deleted 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.Code Snippets
smart_pointer(T* p = NULL)
: pointer(p), refs(new std::size_t(1))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
}void clear(){
if (!--*refs){
std::cout << "deleteting " << *pointer << " at " << pointer << "\n";
delete pointer;
delete refs;
}
}Context
StackExchange Code Review Q#26353, answer score: 33
Revisions (0)
No revisions yet.