reference object when using STL

T

Tony Johansson

Hello Experts!!

This class template and main works perfectly fine.
I have this class template called Handle that has a pointer declared as T*
body;
As you can see I have a reference counter in the class template so I know
how many references I have to the body. In my case it's the Integer wrapper
class which is the body.
This template works for many different types.
The one that I use is named Integer and is a Wrapper for an int. The code
for this is not include
because it doesn't add any futher information.
At the bottom I have a main.
In main I can call the stand alone function addNodeToSTL plus many more to
add nodes to the STL list container named myList1.
Here is the stand-alone function that add nodes to the STL list container.
void addNodeToSTL(list<handle_t>* myList)
{
handle_t myh(new Integer(getValue("Ange vardet som du vill lagga in i
listan/listorna: ")));
myList->push_front(myh);
}

This handle_t is typedef to
typedef Handle<Integer> handle_t;


Now to my question when I add nodes to a STL container what is actually a
referenceobject in my case.

Is it something that STL has internally or is it my class template Handle.?

template<class T>
class Handle
{
public:
Handle()
{
body = new T(0);
ref_count = new int(1);
}

Handle(T* body_ptr) //Constructor
{
body = body_ptr;
cout << "Body adressen = " << body << endl;
ref_count = new int(1);
}

~Handle() //Destructor
{
(*ref_count)--;
if (!*ref_count)
deleteAll();
}

T operator*() const { return *body; }

T& operator*()
{
if (*ref_count > 1)
{
(*ref_count)--;
body = new T(*body);
ref_count = new int(1);
}
return *body;
}

T* operator->()
{
if (*ref_count > 1)
{
(*ref_count)--;
body = new T(*body);
ref_count = new int(1);
}
return body;
}

T* operator->() const { return body; }
bool operator==(const T& h) { return *body == h; }
bool operator==(const Handle& h) { return *body == *h.body; }

Handle(const Handle& h)
{
body = h.body;
ref_count = h.ref_count;
(*ref_count)++;
}

int getRefCount() { return *ref_count; }
void operator()(Handle& h) { cout << *h.body << endl; }

const Handle& operator=(const Handle& h)
{
if (this != &h)
{
(*ref_count)--;
if (!*ref_count)
deleteAll();
ref_count = h.ref_count;
body = h.body;
(*h.ref_count)++;
}
return *this;
}

private:
T* body;
int* ref_count;

void deleteAll()
{
delete body;
body = NULL;
delete ref_count;
ref_count = NULL;
}
};

void addNodeToSTL(list<handle_t>* myList)
{
handle_t myh(new Integer(getValue("Ange vardet som du vill lagga in i
listan/listorna: ")));
myList->push_front(myh);
}
typedef Handle<Integer> handle_t;

int main()
{
list<handle_t>* myList1 = new list<handle_t>;
list<handle_t>* myList2 = new list<handle_t>;
do
{
switch (huvudMeny())

{
case 0: return 0;
case 1: addNodeToSTL(myList1);
break;
case 2: displaySTL(myList1, myList2);
break;
case 3: deleteSelectedValue(myList1);
break;
case 4: deleteAll(myList1);
break;
case 5: findSelectedValue(myList1);
break;
case 6: testOperatorStar(myList1);
break;
case 7: testOperatorArrow(myList1);
break;
case 8: copyList(myList1, myList2);
break;
}
} while(1);
return 0;
}

//Many thanks

//Tony
 
K

Kai-Uwe Bux

Tony said:
Hello Experts!!

This class template and main works perfectly fine.

We shall see about that.
I have this class template called Handle that has a pointer declared as
T* body;
As you can see I have a reference counter in the class template so I know
how many references I have to the body.

Ok. So the idea is to execute

delete body

when the reference count drops to 0.
In my case it's the Integer
wrapper class which is the body.
This template works for many different types.
The one that I use is named Integer and is a Wrapper for an int. The code
for this is not include
because it doesn't add any futher information.
At the bottom I have a main.
In main I can call the stand alone function addNodeToSTL plus many more
to add nodes to the STL list container named myList1.
Here is the stand-alone function that add nodes to the STL list container.
void addNodeToSTL(list<handle_t>* myList)
{
handle_t myh(new Integer(getValue("Ange vardet som du vill lagga in i
listan/listorna: ")));
myList->push_front(myh);
}

This handle_t is typedef to
typedef Handle<Integer> handle_t;


Now to my question when I add nodes to a STL container what is actually a
referenceobject in my case.

Is it something that STL has internally or is it my class template
Handle.?

That, actually is up to the implementation and also might depend on which
container you are using: I would implement std::vector<X> in such a way
that it just stores objects of type X, however std::map<X> would be a tree
structure and the nodes, for which storage would be allocated might look
like this:

template < typename X >
class map_node {

X data;
map_node * right;
map_node * left;
map_node * parent;

....

}; // clas map_node<X>

In any case, your code should not rely on any assumptions about the internal
workings of standard containers nor iterators nor algorithms.



Now for your reference counted pointer:
template<class T>
class Handle
{
public:
Handle()
{
body = new T(0);
ref_count = new int(1);
}

a) Why is T constructible from 0?
b) Why not initialize:

Handle ()
: body ( new T () )
, ref_count ( new int (1) )
{}

c) Still, just being picky: if new int(1) fails, body will leak memory.

So, maybe we are back to not initializing:

Handle () {
std::auto_ptr<T> b_tmp = new T();
ref_count = new int (1);
body = b_tmp.release();
}
Handle(T* body_ptr) //Constructor
{
body = body_ptr;
cout << "Body adressen = " << body << endl;
ref_count = new int(1);
}

There is a similar problem here, although one could argue that in this case
the caller could handle it by catching an exception.
~Handle() //Destructor
{
(*ref_count)--;
if (!*ref_count)

Make that

if ( *ref_count == 0 )

after all that is what you want to communicate to the reader: the number of
references is 0.
deleteAll();
}

T operator*() const { return *body; }

You might consider

T const & operator*() const { return *body; }
T& operator*()
{
if (*ref_count > 1)
{
(*ref_count)--;

-- (*ref_count)
;
body = new T(*body);
ref_count = new int(1);

leak (see above)
}
return *body;
}

This seems to implement an interesting piece of lazy copy semantics.

T* operator->()
{
if (*ref_count > 1)
{
(*ref_count)--;
body = new T(*body);
ref_count = new int(1);
}
return body;
}

Same comments as for T&
T* operator->() const { return body; }

Probably you meant:

T const * operator->() const { return body; }

bool operator==(const T& h) { return *body == h; }
bool operator==(const Handle& h) { return *body == *h.body; }

You might consider forwarding < as well so that you could use std::sort and
std::set for these handles.
Handle(const Handle& h)
{
body = h.body;
ref_count = h.ref_count;
(*ref_count)++;
}

Looks cool.
int getRefCount() { return *ref_count; }
void operator()(Handle& h) { cout << *h.body << endl; }

const Handle& operator=(const Handle& h)
{
if (this != &h)
{
(*ref_count)--;
if (!*ref_count)
deleteAll();
ref_count = h.ref_count;
body = h.body;
(*h.ref_count)++;
}
return *this;
}

Hm, what about:

Hande & operator= ( Handle const & other ) {
Handle dummy ( other );
swap( *this, dummy );
return( *this );
}

and have some friend function

swap ( Handle & a, Handle & b ) {
swap( a.body, b.body );
swap( a.ref_count, b.ref_count );
}

Note how this assignment operator delegates all the tricky stuff to the copy
constructor and the destructor: the ref_counting is done when dummy is
destroyed. Thus you only have to get copy constructor and destructor right.
In particular, if the copy constructor is exception safe, then so is the
assignment operator.
 
T

Tony Johansson

Hello//
Thanks for many good suggestions about the code but I one more question.

This code for assignement operator and cpy-ctor. I thing the order is wrong
I must have statement
ref_count = h.ref_count; after
(*h.ref_count)++;
Now it's before so that's wrong order.
It's the same order mistake in both assignment operator and the cpy-ctor. Do
you agree with me.?
Handle(const Handle& h)
{
body = h.body;
ref_count = h.ref_count;
(*ref_count)++;
}

const Handle& operator=(const Handle& h)
{
if (this != &h)
{
(*ref_count)--;
if (!*ref_count)
deleteAll();
ref_count = h.ref_count;
body = h.body;
(*h.ref_count)++;
}
return *this;
}

//Tony
 
K

Kai-Uwe Bux

I forgot to mention one thing:

Kai-Uwe Bux wrote:

This seems to implement an interesting piece of lazy copy semantics.

Note that this copy semantics implies that your handle is unfit for holding
pointers whose type varies polymorphically. The line:

body = new T(*body);

will cheerfully slice any object derived from T.



Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

Tony said:
Hello//
Thanks for many good suggestions about the code but I one more question.

This code for assignement operator and cpy-ctor. I thing the order is
wrong I must have statement
ref_count = h.ref_count; after
(*h.ref_count)++;
Now it's before so that's wrong order.
It's the same order mistake in both assignment operator and the cpy-ctor.
Do you agree with me.?
Handle(const Handle& h)
{
body = h.body;
ref_count = h.ref_count;
(*ref_count)++;

This works. So do:

ref_count = h.ref_count;
++ (*h.ref_count);

and:

++ (*h.ref_count);
ref_count = h.ref_count;

The only version that fails is:

++ (*ref_count);
ref_count = h.ref_count;


const Handle& operator=(const Handle& h)
{
if (this != &h)
{
(*ref_count)--;
if (!*ref_count)
deleteAll();
ref_count = h.ref_count;
body = h.body;
(*h.ref_count)++;
}
return *this;
}

this works. So would:

(*ref_count)--;
if (!*ref_count)
deleteAll();
ref_count = h.ref_count;
body = h.body;
++(*ref_count);

and

(*ref_count)--;
if (!*ref_count)
deleteAll();
body = h.body;
++(*h.ref_count);
ref_count = h.ref_count;




More generally: suppose you have two pointers a_ptr and b_ptr, then

a_ptr = b_ptr;
do something to *a_ptr;

and

a_ptr = b_ptr;
do something to *b_ptr;

and

do something to *b_ptr;
a_ptr = b_ptr;

are equivalent for reasonably unsurprising values of something and
reasonably dumb pointer types.


Nonetheless, I would strongly recommend to ditch the assignment operator in
favor of a swap-based implementation: the code is much more easy to
understand.



Best

Kai-Uwe Bux

PS: all this breaks in the presence of multiple threads messing around.

PPS: since you have this copy semantics anyway, you could ditch reference
counting alltogether (untested code, might not even compile):

template < typename T >
copy_ptr {

T * raw_ptr;

public:

copy_ptr ( copy_ptr const & other )
: raw_ptr ( new T ( *(other.raw_ptr) ) )
{}

copy_ptr ( T value = T() )
: raw_ptr ( new T ( value ) )
{}

~copy_ptr ( void ) {
delete raw_ptr;
}

copy_ptr & operator= ( copy_ptr const & other ) {
copy_ptr dummy ( other );
swap( this->raw_ptr, dummy.raw_ptr );
return( *this );
}

T const * operator-> ( void ) const {
return( raw_ptr );
}

T * operator-> ( void ) {
return( raw_ptr );
}

T const & operator-> ( void ) const {
return( raw_ptr );
}

T & operator-> ( void ) {
return( raw_ptr );
}

bool operator< ( copy_ptr const & other ) const {
return( *(this->raw_ptr) < *(other.raw_ptr) );
}

bool operator== ( copy_ptr const & other ) const {
return( *(this->raw_ptr) == *(other.raw_ptr) );
}

}; // copy_ptr<T>

Note a few differences:

a) No constructor from T* is provided. This guarantees that there always
will be a valid pointee since construction from 0 is prevented.

b) Every assignment creates a copy of the pointee. The advantage is: we do
not need reference counting, the disadvantage is a certain overhead cost
from assignments.

c) Since the class contains only one pointer field, the auto_ptr trickery is
no longer needed to deal with throws from new().
 
G

Greg

Kai-Uwe Bux said:
I forgot to mention one thing:

Kai-Uwe Bux wrote:



Note that this copy semantics implies that your handle is unfit for holding
pointers whose type varies polymorphically. The line:

body = new T(*body);

will cheerfully slice any object derived from T.



Best

Kai-Uwe Bux

No, there is no slicing, just a memory leak. For one, we know body is a
T* since the class initialized it that way. Second, the argument to the
constructor, even though it is dereferenced is still being passed by
reference. Slicing requires copying by value. Third, would body really
be constructed any differently whether initialized with a T or a
subclass of T? I don't see any realistic way that it could matter, but
the question is moot anyway owing to the first two reasons.

Greg
 
K

Kai-Uwe Bux

Greg said:
No, there is no slicing, just a memory leak. For one, we know body is a
T* since the class initialized it that way. Second, the argument to the
constructor, even though it is dereferenced is still being passed by
reference. Slicing requires copying by value. Third, would body really
be constructed any differently whether initialized with a T or a
subclass of T? I don't see any realistic way that it could matter, but
the question is moot anyway owing to the first two reasons.

Greg


Let me exsplain what I mean: Below you find the code for Handle<T> as given
by the OP. Then I demonstrate in main() a case of what I consider slicing.
Now, I might be mistaken about terminology, but I am pretty sure that the
program below demonstrates Handle<T> to be unfit for polymorphic use.


#include <iostream>
using namespace std;

template<class T>
class Handle
{
public:
Handle()
{
body = new T(0);
ref_count = new int(1);
}

Handle(T* body_ptr) //Constructor
{
body = body_ptr;
cout << "Body adressen = " << body << endl;
ref_count = new int(1);
}

~Handle() //Destructor
{
(*ref_count)--;
if (!*ref_count)
deleteAll();
}

T operator*() const { return *body; }

T& operator*()
{
if (*ref_count > 1)
{
(*ref_count)--;
body = new T(*body);
ref_count = new int(1);
}
return *body;
}

T* operator->()
{
if (*ref_count > 1)
{
(*ref_count)--;
body = new T(*body);
ref_count = new int(1);
}
return body;
}

T* operator->() const { return body; }
bool operator==(const T& h) { return *body == h; }
bool operator==(const Handle& h) { return *body == *h.body; }

Handle(const Handle& h)
{
body = h.body;
ref_count = h.ref_count;
(*ref_count)++;
}

int getRefCount() { return *ref_count; }
void operator()(Handle& h) { cout << *h.body << endl; }

const Handle& operator=(const Handle& h)
{
if (this != &h)
{
(*ref_count)--;
if (!*ref_count)
deleteAll();
ref_count = h.ref_count;
body = h.body;
(*h.ref_count)++;
}
return *this;
}

private:
T* body;
int* ref_count;

void deleteAll()
{
delete body;
body = NULL;
delete ref_count;
ref_count = NULL;
}
};


struct Base {

virtual
~Base () {}

virtual
int get_value ( void ) const { return 314; }

};

struct Derived : public Base {

virtual
~Derived () {}

virtual
int get_value ( void ) const { return 272; }

};


typedef Handle< Base > base_handle;

int main() {
base_handle a_ptr ( new Derived() );
base_handle b_ptr ( new Base() );
b_ptr = a_ptr;

std::cout << b_ptr->get_value() << " != " << a_ptr->get_value() << '\n';
}


It so happens that the information about the dynamic type of the object is
lost in the assignment:

news_group> a.out
Body adressen = 0x804a050
Body adressen = 0x804a070
272 != 314



Best

Kai-Uwe Bux
 
G

Greg

Kai-Uwe Bux said:
Let me exsplain what I mean: Below you find the code for Handle<T> as given
by the OP. Then I demonstrate in main() a case of what I consider slicing.
Now, I might be mistaken about terminology, but I am pretty sure that the
program below demonstrates Handle<T> to be unfit for polymorphic use.

Technically, it's not slicing since Derived remains intact, but the
overall effect is the same. The fix for this problem and the memory
leak is not to allocate a new T in the * and -> operators but to simply
return the existing body member variable.

Greg
 
K

Kai-Uwe Bux

Greg said:
[snip]
Technically, it's not slicing since Derived remains intact, but the
overall effect is the same.

Since you seem to know: what is the technical definition of slicing?
The fix for this problem and the memory
leak is not to allocate a new T in the * and -> operators but to simply
return the existing body member variable.

a) Where do you see a memory leak?

b) Would your fix not change the semantics? Consider

Handle< T > a_ptr;
Handle< T > b_ptr;

...

a_ptr = b_ptr;
a_ptr->some_non_const_function();

The current implementation will in the last line create an individual copy
of the data just for a_ptr to own. Thus *b_ptr will not be affected by the
call. Your fix would change that.



Best

Kai-Uwe Bux
 
G

Greg

Kai-Uwe Bux said:
Greg said:
Kai-Uwe Bux said:
Greg wrote:


Kai-Uwe Bux wrote:
I forgot to mention one thing:

Kai-Uwe Bux wrote:


T& operator*()
{
if (*ref_count > 1)
{
(*ref_count)--;
body = new T(*body);
ref_count = new int(1);
}
return *body;
}
[snip]
Technically, it's not slicing since Derived remains intact, but the
overall effect is the same.

Since you seem to know: what is the technical definition of slicing?

Slicing occurs when an instance of a derived class is copied into a
variable of a base class type. Since the base class is likely smaller
than any of its derived classess, the derived class will not fit in the
variable. The portion of the derived class that does not fit is thereby
"sliced" off during the copy.

There is an example of slicing in one of Handle's operators:

T operator*() const { return *body; }

Here the contents of body are copied into a variable of type T. If body
is actually a pointer to a derived class of T, then the object will be
"sliced" because there is only room for a T in the type being returned.
a) Where do you see a memory leak?

b) Would your fix not change the semantics? Consider

Handle< T > a_ptr;
Handle< T > b_ptr;

...

a_ptr = b_ptr;
a_ptr->some_non_const_function();

The current implementation will in the last line create an individual copy
of the data just for a_ptr to own. Thus *b_ptr will not be affected by the
call. Your fix would change that.

The current semantics of Handle are inconsistent. The const version of
Handle's -> operator returns a non-const T* object just like the non
const -> operator does. The client can therefore effectively modify
body without triggering the copy-on-write operation. The result will be
that some holder of a Handle will find that its value has unexpectedly
changed.

Greg
 
K

Kai-Uwe Bux

Greg said:
Slicing occurs when an instance of a derived class is copied into a
variable of a base class type. Since the base class is likely smaller
than any of its derived classess, the derived class will not fit in the
variable. The portion of the derived class that does not fit is thereby
"sliced" off during the copy.

Hm, that was my understanding of slicing, too. And I think it would happen
in:

Derived * d_ptr = new Derived ( ... ); // how much memory is allocated?
Base * b_ptr = new Base ( *d_ptr ); // how much now?

After all, new Base will allocate just sizeof(Base) bytes of memory and then
in place construct something into that region from *d_ptr. Generally, the
sizes won't match. The following program illustrates the phenomenon:

#include <iostream>

struct Base {

int i;

virtual ~Base () {}

virtual
std::eek:stream & dump ( std::eek:stream & ostr ) const {
return( ostr << "base" );
}

};

std::eek:stream & operator<< ( std::eek:stream & ostr,
Base const & obj ) {
return( obj.dump( ostr ) );
}

struct Derived : public Base {

int j;

virtual ~Derived () {}

virtual
std::eek:stream & dump ( std::eek:stream & ostr ) const {
return( ostr << "derived" );
}

};

void b_print_pointer ( Base const * ptr ) {
std::cout << "pass b-pointer: " << *ptr
<< '\n';
}

void b_print_reference ( Base const & ref ) {
std::cout << "pass b-reference: " << ref
<< '\n';
}

void b_print_value ( Base val ) {
std::cout << "pass b-value: " << val
<< '\n';
}

void b_print_copy_ptr ( Base const * ptr ) {
std::auto_ptr< Base > local_copy ( new Base ( *ptr ) );
std::cout << "copy b-pointer: " << *local_copy
<< '\n';
}

void b_print_copy_ref ( Base const & ref ) {
std::auto_ptr< Base > local_copy ( new Base ( ref ) );
std::cout << "copy b-reference: " << *local_copy
<< '\n';
}

void d_print_pointer ( Derived const * ptr ) {
std::cout << "pass d-pointer: " << *ptr
<< '\n';
}

void d_print_reference ( Derived const & ref ) {
std::cout << "pass d-reference: " << ref
<< '\n';
}

void d_print_value ( Derived val ) {
std::cout << "pass d-value: " << val
<< '\n';
}

void d_print_copy_ptr ( Derived const * ptr ) {
std::auto_ptr< Base > local_copy ( new Base ( *ptr ) );
std::cout << "copy d-pointer: " << *local_copy
<< '\n';
}

void d_print_copy_ref ( Derived const & ref ) {
std::auto_ptr< Base > local_copy ( new Base ( ref ) );
std::cout << "copy d-reference: " << *local_copy
<< '\n';
}

int main ( void ) {
Derived d;
b_print_pointer( &d );
b_print_reference( d );
b_print_value( d );
b_print_copy_ptr( &d );
b_print_copy_ref( d );
d_print_pointer( &d );
d_print_reference( d );
d_print_value( d );
d_print_copy_ptr( &d );
d_print_copy_ref( d );
}


BTW, this is exactly what is going on, e.g., in the code for
There is an example of slicing in one of Handle's operators:

T operator*() const { return *body; }

Here the contents of body are copied into a variable of type T. If body
is actually a pointer to a derived class of T, then the object will be
"sliced" because there is only room for a T in the type being returned.

Agreed.

The current semantics of Handle are inconsistent. The const version of
Handle's -> operator returns a non-const T* object just like the non
const -> operator does. The client can therefore effectively modify
body without triggering the copy-on-write operation. The result will be
that some holder of a Handle will find that its value has unexpectedly
changed.

Agreed, I had pointed that out to the OP in my first post. The fix for that,
however, is just changing the return type for the const operators. This
way, you preserve the intended copy-on-write semantics. [Whether cow in a
smart pointer is a good idea at all, is a different matter; and whether
deep copy semantics for a smart pointer class not supporting polymorphism
makes any sense, is at least debatable. However, those are completely
different issues.]


Thanks a lot for the explanation
and best regards

Kai-Uwe Bux
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top