I have written a smart pointer class called ptr<X>
Here are two ways it could be used:
void foo(ptr<X> p)
{
// use p here
}
void foo(const ptr<X>& p)
{
ASSERT(&p != null);
// use p here
}
Based on my work it seems that boths ways are equally efficient
because the size of the ptr<X> class is 4 bytes (enough to hold a
pointer variable) and therefore the variable can be passed inside a
register. Therefore it makes sense to use the first way because it is
simpler. The following code is the source code for my ptr<X> template
class:
template<class X>
class ptr
{
private:
X* inner_ptr;
public:
ptr()
{
inner_ptr = null;
}
ptr(const ptr& p)
{
ASSERT(&p != null);
inner_ptr = p.inner_ptr;
if (inner_ptr != null)
{
inner_ptr->ref_count++;
}
}
ptr& operator = (const ptr& p)
{
ASSERT(this != null);
ASSERT(&p != null);
if (p.inner_ptr != null) // protect against s = s
{
p.inner_ptr->ref_count++;
}
if (inner_ptr != null)
{
assert(inner_ptr->ref_count > 0); // protect against double
deletions
inner_ptr->ref_count--;
if (inner_ptr->ref_count <= 0) // protect against case < 0
{
inner_ptr->ref_count = 1; // protect against internal
deletions
delete inner_ptr;
}
}
inner_ptr = p.inner_ptr;
return *this;
}
~ptr()
{
ASSERT(this != null);
if (inner_ptr != null)
{
assert(inner_ptr->ref_count > 0); // protect against double
deletions
inner_ptr->ref_count--;
if (inner_ptr->ref_count <= 0) // protect against case < 0
{
inner_ptr->ref_count = 1; // protect against internal
deletions
delete inner_ptr;
}
#ifndef NDEBUG
inner_ptr = null;
#endif /* NDEBUG */
}
}
ptr(X* x)
{
inner_ptr = x;
if (inner_ptr != null)
{
inner_ptr->ref_count++;
}
}
ptr& operator = (X* x)
{
ASSERT(this != null);
if (inner_ptr != null)
{
assert(inner_ptr->ref_count > 0); // protect against double
deletions
inner_ptr->ref_count--;
if (inner_ptr->ref_count <= 0) // protect against case < 0
{
inner_ptr->ref_count = 1; // protect against internal
deletions
delete inner_ptr;
}
}
inner_ptr = x;
if (inner_ptr != null)
{
inner_ptr->ref_count++;
}
return *this;
}
///
/// NOTE: Be careful not to store this pointer in a long-term
/// variable or else it will wreck the auto-GC system...
/// (Therefore the Lisp AutoGC system should search for such
pointers)
///
/// NOTE: fails if pointer is null, if null is OK, use method
get_ptr()
///
X* operator -> () const
{
ASSERT(this != null);
ASSERT(inner_ptr != null);
return inner_ptr;
}
X& operator * () const
{
ASSERT(this != null);
ASSERT(inner_ptr != null);
return *inner_ptr;
}
X* get_ptr() const
{
ASSERT(this != null);
return inner_ptr;
}
///
/// NOTE: these methods are like Lisp's eq function (not Lisp's
equal function)
///
/// NOTE: g++ does not optimise these functions very well...
///
friend bool operator == (const ptr& p1, const ptr& p2)
{
if ((&p1 == null) && (&p2 == null))
{
return true;
}
else if ((&p1 == null) || (&p2 == null))
{
return false;
}
else
{
return (p1.inner_ptr == p2.inner_ptr);
}
}
friend bool operator != (const ptr& p1, const ptr& p2)
{
return !(p1 == p2);
}
};
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
davin.pearson (9)
|
9/29/2007 9:01:23 PM |
|
Davin Pearson wrote:
> I have written a smart pointer class called ptr<X>
> Here are two ways it could be used:
>
> void foo(ptr<X> p)
> {
> // use p here
> }
>
> void foo(const ptr<X>& p)
> {
> ASSERT(&p != null);
> // use p here
> }
Um, why that assertion? If you have a reference, and taking the address of
that reference yields NULL, you have undefined behaviour (NULL pointer
dereference) already anyway!
> Based on my work it seems that boths ways are equally efficient
> because the size of the ptr<X> class is 4 bytes (enough to hold a
> pointer variable) and therefore the variable can be passed inside a
> register. Therefore it makes sense to use the first way because it is
> simpler.
The copying overhead should be small anyway. The question is what it should
mean, e.g. passing a shared_ptr or an auto_ptr it would mean different
things.
> The following code is the source code for my ptr<X> template
> class:
>
> template<class X>
> class ptr
> {
> private:
> X* inner_ptr;
>
> public:
> ptr()
> {
> inner_ptr = null;
> }
- Members of a 'class' are private by default, no need to repeat it.
- Where is the definition of 'null'? BTW, ASSERT() is also not standard, it
is called assert() there!
- You should use initialisation instead of assignment.
> ptr& operator = (const ptr& p)
> {
> ASSERT(this != null);
> ASSERT(&p != null);
> if (p.inner_ptr != null) // protect against s = s
> {
> p.inner_ptr->ref_count++;
> }
> if (inner_ptr != null)
> {
> assert(inner_ptr->ref_count > 0); // protect against
> double deletions
> inner_ptr->ref_count--;
> if (inner_ptr->ref_count <= 0) // protect against case < 0
> {
> inner_ptr->ref_count = 1; // protect against
> internal deletions
> delete inner_ptr;
> }
> }
> inner_ptr = p.inner_ptr;
> return *this;
> }
- Check for self-assignment is typically done like 'if(this!=&other)'.
- You could have written this much easier if you had just created a
temporary copy of 'p' and then swapped it with the asignee.
- I don't understand what you mean with 'internal deletions' and why you set
the refcouter to one again.
- I don't understand why you are checking for a negative refcount. Rather,
you should assert that this doesn't happen. You will have to use different
means than the increment/decrement operators when used in a multithreaded
environment tohugh.
- Prefer prefix increment to postfix increment.
> ptr(X* x)
> {
> inner_ptr = x;
> if (inner_ptr != null)
> {
> inner_ptr->ref_count++;
> }
> }
Consider making this one explicit to avoid implicit conversions from a
pointer to a ptr<X>. The problem occurs when you change a function:
void foo(X* px);
to
void foo( ptr<X> px);
If you used to call it like this:
X x;
foo(&x);
this will still compile but suddenly you have a ptr<X> taking ownership
of 'x' and deleting it when foo() returns. An explicit constructor makes it
mandatory to take according actions, i.e. increment the refcounter in
advance so that the ptr<X> doesn't reach zero.
> ptr& operator = (X* x)
> {[...]}
Same as other assignment operator overload: use a temporary and swap with
it. Obviously, you will need a swap function for that:
friend void swap( ptr& p1, ptr& p2) {
std::swap( p1.inner_ptr, p2.inner_ptr);
}
> friend bool operator == (const ptr& p1, const ptr& p2)
> {
> if ((&p1 == null) && (&p2 == null))
> {
> return true;
> }
> else if ((&p1 == null) || (&p2 == null))
> {
> return false;
> }
> else
> {
> return (p1.inner_ptr == p2.inner_ptr);
> }
> }
You are checking the addresses of the ptr<X> objects here, but those must
not be null as explained above! The only thing this function should do is
return p1.inner_ptr == p2.inner_ptr;
...and then I'm pretty sure that GCC will optimise this pretty well.
> friend bool operator != (const ptr& p1, const ptr& p2)
> {
> return !(p1 == p2);
> }
No need to make this one a friend, it doesn't use any private parts. Note
that even operator== could have been implemented with just the public
interface.
One final note: boost::intrusive_ptr does pretty much all that your pointer
does and a bit more. If you intend yours for production code, I'd at least
take a look at that one for inspiration if not completely switch to it.
Uli
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Ulrich
|
9/30/2007 10:52:44 AM
|
|
On Sep 29, 11:01 pm, Davin Pearson <davin.pear...@gmail.com> wrote:
> I have written a smart pointer class called ptr<X>
> Here are two ways it could be used:
>
> void foo(ptr<X> p)
> {
> // use p here
>
> }
>
> void foo(const ptr<X>& p)
> {
> ASSERT(&p != null);
> // use p here
>
> }
>
> Based on my work it seems that boths ways are equally efficient
> because the size of the ptr<X> class is 4 bytes (enough to hold a
> pointer variable) and therefore the variable can be passed inside a
> register. Therefore it makes sense to use the first way because it is
> simpler.
Did you consider that the first way would cause the ref_counter to be
incremented? And then decremented on function return? This probably is
more expensive than the reference
Lance
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Lance
|
9/30/2007 11:07:13 AM
|
|
On Oct 1, 5:52 am, Ulrich Eckhardt <dooms...@knuut.de> wrote:
> Davin Pearson wrote:
> > I have written a smart pointer class called ptr<X>
> > Here are two ways it could be used:
>
> > void foo(ptr<X> p)
> > {
> > // use p here
> > }
>
> > void foo(const ptr<X>& p)
> > {
> > ASSERT(&p != null);
> > // use p here
> > }
>
> Um, why that assertion? If you have a reference, and taking the address of
> that reference yields NULL, you have undefined behaviour (NULL pointer
> dereference) already anyway!
>
Here is some code that explains why you need to
take the address of the reference:
#include <...>
class X
{
int ref_count;
};
void foo(const ptr<X>& p)
{
assert(&p != null);
}
int main()
{
ptr<X>* p = null;
foo(*p);
exit(EXIT_SUCCESS);
}
The assertion fails because you are passing in null as a reference.
After some empirical testing I have found out that
foo(const ptr<X>&) is faster than foo(ptr<X>).
Therefore I will use the first alternative
in my code.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Davin
|
10/1/2007 8:13:50 AM
|
|
Hi
Davin Pearson wrote:
> Here is some code that explains why you need to
> take the address of the reference:
[...]
> int main()
> {
> ptr<X>* p = null;
I assume ptr<X>* p = 0? Or a preceding #define null 0?
> foo(*p);
> exit(EXIT_SUCCESS);
> }
>
> The assertion fails because you are passing in null as a reference.
Well... as Ulrich noted, this is undefined behavior, whether you include an
assertion or you don't. (The "*p" makes it undefined, because you are
dereferencing a null pointer.) The program might crash before the
assertion, it might not crash at all, your assertion might print "Happy New
Year!"... the possibilities are endless.
It makes no sense to check whether undefined behavior has been invoked,
because at that point there is no guarantee that the check itself will do
anything sensible.
Markus
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Markus
|
10/1/2007 12:58:35 PM
|
|
Markus Moll wrote:
[ about 'assert( this!=0);' and similar things ]
> It makes no sense to check whether undefined behavior has been invoked,
> because at that point there is no guarantee that the check itself will do
> anything sensible.
Sorry, but I beg to differ. The whole 'checked STL' idea is about giving a
proper diagnostic for things that the C++ standard only calls 'undefined'.
However, and that's my reason for the initial comment on that, a null
pointer dereference is by far not as hard to find as an iterator overflow,
so it doesn't merit an assertion. Whether you have an assertion that fails
and dumps you into the debugger or whether it's access to the object which
does it doesn't matter, hence those assertions aren't worth the effort for
me and just clutter up the code.
BTW: I don't mean that checking should be performed, but two different
versions, one with speed the other with diagnostics are a good idea.
Uli
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Ulrich
|
10/1/2007 3:49:31 PM
|
|
On Oct 1, 3:13 pm, Davin Pearson <davin.pear...@gmail.com> wrote:
> On Oct 1, 5:52 am, Ulrich Eckhardt <dooms...@knuut.de> wrote:
> > Davin Pearson wrote:
> > > void foo(const ptr<X>& p)
> > > {
> > > ASSERT(&p != null);
> > > // use p here
> > > }
>
> > Um, why that assertion? If you have a reference, and taking the address of
> > that reference yields NULL, you have undefined behaviour (NULL pointer
> > dereference) already anyway!
>
> Here is some code that explains why you need to
> take the address of the reference:
>
> ...
> int main()
> {
> ptr<X>* p = null;
> foo(*p);
> exit(EXIT_SUCCESS);
> }
>
> The assertion fails because you are passing in null as a reference.
As Markus Moll pointed out the standard says that this
has undefined behaviour. On the other hand, if the code
is only intended for a specific platform and compiler,
assertions that this!=NULL may sometimes be useful. Then
again, why should you do it in a smart pointer class?
How often are you going to use a pointer-to-pointer?
Note that the following will not trigger the assertion
in copy constructor on any platform and compiler I know:
struct X{
int a;
ptr<int> p;
};
int main(){
X* pX = NULL;
ptr<int> test(pX->p); //< dereferencing NULL here
}
> After some empirical testing I have found out that
> foo(const ptr<X>&) is faster than foo(ptr<X>).
> Therefore I will use the first alternative
> in my code.
Well, when taking the argument by reference, you avoid
checking for NULL and incrementing/decrementing the
ref_count. So this is the expected result. (But you
never know what the compiler can optimize or fail to
optimize just because of the little change. It all
depends on the context.)
Cheers,
Vladimir Marko
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Vladimir
|
10/1/2007 5:43:46 PM
|
|
On Oct 2, 7:58 am, Markus Moll <markus.m...@esat.kuleuven.ac.be>
wrote:
> Davin Pearson wrote:
> > Here is some code that explains why you need to
> > take the address of the reference:
> [...]
> > int main()
> > {
> > ptr<X>* p = null;
>
> I assume ptr<X>* p = 0? Or a preceding #define null 0?
Yes that's correct.
>
> > foo(*p);
> > exit(EXIT_SUCCESS);
> > }
>
> > The assertion fails because you are passing in null as a reference.
>
> Well... as Ulrich noted, this is undefined behavior, whether you include an
> assertion or you don't. (The "*p" makes it undefined, because you are
> dereferencing a null pointer.) The program might crash before the
> assertion, it might not crash at all, your assertion might print "Happy New
> Year!"... the possibilities are endless.
>
For my purposes it is better to get an assertion failure than a crash
because you get
more information with an assertion failure than a crash.
The *p works fine on my machine. The program would crash if you try
to use
p like so:
ptr<X> q = p;
By putting the assertion before you use p causes you to get an
assertion failure rather than a program crash.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Davin
|
10/1/2007 9:39:28 PM
|
|
Hi
Ulrich Eckhardt wrote:
> Sorry, but I beg to differ. The whole 'checked STL' idea is about giving a
> proper diagnostic for things that the C++ standard only calls 'undefined'.
Yes, but that only works if your implementation gives you some guarantees
about that. And then it is not portable code any more. Don't misunderstand
me: If that's fine for you, it's fine for me. But in general, such
assertions are completely pointless.
Markus
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Markus
|
10/2/2007 4:30:44 AM
|
|
Hi
Davin Pearson wrote:
> For my purposes it is better to get an assertion failure than a crash
> because you get
> more information with an assertion failure than a crash.
(Hint: debugger)
> The *p works fine on my machine. The program would crash if you try
> to use
> p like so:
>
> ptr<X> q = p;
>
> By putting the assertion before you use p causes you to get an
> assertion failure rather than a program crash.
Well, maybe on your machine and until now. But unless you can find something
about that in your compiler's documentation, there is always a good chance
that with some border cases, it will not trigger the assertion but instead
crash (or do whatever else). IMO, assertions like these give you a false
feeling of safety.
Markus
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Markus
|
10/2/2007 4:30:55 AM
|
|
Markus Moll wrote:
> Ulrich Eckhardt wrote:
>> Sorry, but I beg to differ. The whole 'checked STL' idea is about giving
>> a proper diagnostic for things that the C++ standard only calls
>> 'undefined'.
>
> Yes, but that only works if your implementation gives you some guarantees
> about that. And then it is not portable code any more. Don't misunderstand
> me: If that's fine for you, it's fine for me. But in general, such
> assertions are completely pointless.
You mean that
class vector {
T const& operator[](size_type i) const {
assert(i<size);
return *(begin()+i);
}
...
};
is useless or not portable? Note that in this case, no undefined behaviour
occurs when this function is called with an invalid index and NDEBUG is not
defined. In the case that the OP used, that UB has already occurred when it
is detected via some implementation-specific means.
Uli
--
Sator Laser GmbH
Geschäftsführer: Ronald Boers, Amtsgericht Hamburg HR B62 932
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Ulrich
|
10/2/2007 12:53:39 PM
|
|
Hi
Ulrich Eckhardt wrote:
> Markus Moll wrote:
>> Yes, but that only works if your implementation gives you some guarantees
>> about that. And then it is not portable code any more. Don't
>> misunderstand me: If that's fine for you, it's fine for me. But in
>> general, such assertions are completely pointless.
>
> You mean that
>
> class vector {
> T const& operator[](size_type i) const {
> assert(i<size);
> return *(begin()+i);
> }
> ...
> };
>
> is useless or not portable?
There seems to be a misunderstanding. No, of course I _don't_ think that's
useless, nor do I think it is not portable. What I _said_ was that I think
that
>> It makes no sense to check whether undefined behavior has been invoked
This is quite different, because it means checking for possible undefined
behavior when in fact the behavior already is undefined (including the
behavior of any such check). It's as pointless as constantly checking
whether you are still alive or already dead. There is no guarantee that you
will ever notice that you died... It might be different if God spoke to you
before and gave you those guarantees. ;-) (Even then it's not necessarily
portable across beliefs... euh... okay, I'll stop here)
> Note that in this case, no undefined behaviour
> occurs when this function is called with an invalid index and NDEBUG is
> not defined. In the case that the OP used, that UB has already occurred
> when it is detected via some implementation-specific means.
Well... the question remains whether it really is an implementation-specific
means, i.e. whether it is detected. And "relying on implementation-specific
guarantees" and "portable" mean different things to me.
Markus
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Markus
|
10/3/2007 5:53:51 AM
|
|
|
11 Replies
191 Views
(page loaded in 0.111 seconds)
Similiar Articles: ptr versus const ptr& - comp.lang.c++.moderatedI have written a smart pointer class called ptr Here are two ways it could be used: void foo(ptr p) { // use p here } void foo(const ptr& p) {... Inserting IPv4 header checksum into dummy IP header - comp.lang.c ...By the way, you're casting away const in the memcpy call. > memcpy((void *)&ptr[10], &ip_checksum, 2); where ptr is a pointer to readonly memory. 128-bit MMX versus 32-bit memory copy - comp.lang.asm.x86 ...... ptr[addr1+0] movdqa xmmword ptr[addr2+0], xmm0 movdqa xmm0, xmmword ptr ... Add +/- 10 cycles variance to each due to rdtsc executing out of order and other ... QWORD PTR error on Intel Mac - comp.lang.asm.x86What is Difference between const int i and int const i - comp.lang ... QWORD PTR error ... error ... end if call C_F_POINTER(address, temp_ptr ... mex on 64 bits mac os 10.6.3 ... How to port 32 bit unsigned char* to 64 bit - comp.lang.c++ ...> In 64 bit AMD programming the data type unsigned INT_PTR (unsigned > int*) is UINT ... actually modified (and logically, should be a value, or at least a reference to const ... How to simulate variadic templates? - comp.lang.c++.moderated ...template< class T > class Ptr; template< class ... company's build system is tied down to MS Visual Studio ... with short-circuit primitive operator&& versus ..... const& rX0 ... const reference typedef compile error: - comp.lang.c++.moderated ...However, when you say const ref x; const applied to ref, the reference rather than ... value_type; typedef value_type* ptr; typedef const value_type* const_ptr ... Neatest way to get the end pointer? - comp.lang.cFor example: int my_array[X]; int *p = my_array; int const *const pend = my_array + sizeof ... (Now, the vice versa applies: ptr = 0 is legal, but > ((unsigned ... Const constructor - comp.lang.c++.moderated... class my_pointer; my_pointer(T* p) : ptr(p) {} template<typename U> requires std::Convertible<U*,T*> my_pointer(my_pointer<U> const& x) : ptr(x.ptr ... writing robust software? - comp.lang.c++.moderatedWhat about the combination of shared_ptr and weak_ptr? ... for statement as in: std::vector<T> v = ..; for (const ... std::vector<int> x(5); starts off with five default ... segmentation violation detected Matlab 2009b - comp.soft-sys ...... xnu-1504.7.4~1/RELEASE_I386 i386 Window System: The X ... mcr::runtime::InterpreterThread::Impl::run(boost::shared_ptr<mcr::runtime::InterpreterThread::Impl> const ... Floating point problem (again) - comp.lang.asm.x86... string "-6.20001" (00409038) call _atof (004010e7) fstp dword ptr ... esp,8 fnstsw ax test ah,1 je main+34h (00401034) 10 ... std::exception::what() - comp.lang.c++.moderatedThe std::exception object (and, therefore, the const char * returned by its what ... You need the C++0x std::exception_ptr support. std::current_exception captures the ... Get CPU usage and temperature - comp.os.ms-windows.programmer ...... ULONG HighestPhysicalPageNumber; ULONG AllocationGranularity; ULONG_PTR ... SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; SYSTEM_INFO SysInfo; const int ... Solution to "cannot instantiate abstract class due to following ...> class B { > public: > virtual A myNumber() const ... or a self-defined handle-type (boost::shared_ptr also ... Code class A { int x ... cannot instantiate abstract ... ptr versus const ptr& - comp.lang.c++.moderated | Computer GroupI have written a smart pointer class called ptr Here are two ways it could be used: void foo(ptr p) { // use p here } void foo(const ptr& p) {... scoped_ptr versus auto_ptr C++/VB - C++/VB Discussion List Tuesday ...(At least in Visual Studio 2005, I am not sure if this is impl. dependent) using std::auto_ptr; auto_ptr<int> pAuto = new int(42); // Wrong! const int x = *pAuto ... 7/20/2012 12:06:21 PM
|