Click to See Complete Forum and Search --> : Help with Vectors of Classes and Dervived Classes


d4m
May 1st, 2005, 05:48 PM
Parent Class is Component
Child Classes are Pump, Water, System, Column, Environment

Since all my objects are Components, can I make an vector of Components and use it for any type of Object which I have?

i.e.

vector<CComponent> ComponentList
CPump NewPump

ComponentList.push_back(NewPump)

Is this something doable?

Also

If I wanted to dynamically allocate Components while the program is running, do I have to allocate the Component dynamically, or is that handled within the vector when I push_back?

wien
May 1st, 2005, 06:01 PM
Since all my objects are Components, can I make an vector of Components and use it for any type of Object which I have?Nope. In C++ that won't work. It will compile and run, but what is actually stored in your vector will be copies of the CComponent part of your other classes. The objects will be sliced.vector<CComponent> ComponentList
CPump NewPump

ComponentList.push_back(NewPump)

Is this something doable?Yes it is, but not that way for the reason explained above. You need to store pointers to dynamically allocated objects instead.vector<CComponent*> ComponentList
ComponentList.push_back(new CPump);But now remember to delete the object before you remove it from the vector, or let the vector go out of scope.delete ComponentList.back(); // Delete the last object
ComponentList.pop_back(); // remove the pointer to it from the vectorIf I wanted to dynamically allocate Components while the program is running, do I have to allocate the Component dynamically, or is that handled within the vector when I push_back?All memory allocation of the object type you store will be handled by the vector. It will not though handle memory pointed to by pointers you store in it. (hence the delete above is needed)

Ejaz
May 1st, 2005, 11:18 PM
Also have a look at Builder (http://home.earthlink.net/~huston2/dp/builder.html) design pattern.

exterminator
May 4th, 2005, 07:53 AM
An important point to notice while popping members from the vector -

Wein suggested :

<CODE>
delete ComponentList.back(); // Delete the last object
ComponentList.pop_back(); // remove the pointer to it from the vector
</CODE>

When u have such a vector, its quite tempting to pop the elements and then call the delete on them. This can be disastrous.

The reason - because the objects inside a vector are copies of the original object that you had pushed into the vector. The members that you force to pop out of the vector are copies too !

This creates a memory leak situation. Bang !

Regards,
Exterminator

wien
May 4th, 2005, 08:41 AM
In this case that wouldn't matter. The objects here are pointers, so wheter you delete the original pointer or a copy of it doesn't matter. They both point to the same address in memory.

Or did I misinterpret your post?

HighCommander4
May 4th, 2005, 06:16 PM
The members that you force to pop out of the vector are copies too !


I'm a little confused... pop_back() doesn't return a copy of the popped object, it simply removes it from the vector...

Arjay
May 4th, 2005, 07:34 PM
Also have a look at Builder (http://home.earthlink.net/~huston2/dp/builder.html) design pattern.Are there any code samples available for the link?

Arjay

Ejaz
May 4th, 2005, 11:16 PM
Are there any code samples available for the link?

Arjay

There (http://home.earthlink.net/~huston2/dp/BuilderDemosCpp) it is. :thumb:

exterminator
May 5th, 2005, 04:17 AM
In this case that wouldn't matter. The objects here are pointers, so wheter you delete the original pointer or a copy of it doesn't matter. They both point to the same address in memory.


Right. In this case that wont be the matter of worry since we are storing the pointers to the objects. I guess I was referring to objects being stored in the vector scenario in my post.


I'm a little confused... pop_back() doesn't return a copy of the popped object, it simply removes it from the vector...


Yes, HighCommander4. I was not clear. What i said is what u interpreted and that was wrong. So I was wrong.

What I really meant was something like this:


#include<iostream.h>
#include<string.h>
#include<vector>

using std::vector;
using std::string;

class myInt{
protected:
int i;
public:
myInt(int i):i(i){
}
int getMyInt(){
return i;
}
};

int main(){
vector<myInt> intvector;
myInt *intA = new myInt(10);
for (int loopindex = 0; loopindex<10;loopindex++){
//I create myInt object by dynamically allocating memory and push them into the vector
myInt *newmyint = new myInt(loopindex);
intvector.push_back(*newmyint); //In this case what goes in the vector is a copy of the original newmyint object
//need to call a delete on newmyint here - else memory leak;
delete newmyint;
}

for (int j=0;j<10;j++){

cout << intvector[j].getMyInt()<<endl;

}

//Just use one of the two for loops below at a time... to get the difference in the approaches

//One way to clear the vector
//This is what was suggested by Wein.
for(int loopindex1 = 0;loopindex1<10;loopindex1++){
//do whatever processing required on the last member or whatever
try{
//here normal pop would do...
intvector.pop_back();
}catch(char* str){
cout << str;
}
}

//Other way of clearing the vector
//I dont know why I would do this but just in case.
for(int loopindex2 = 0; intvector.size()>0 && loopindex2<10;loopindex2++){
*intA = intvector[intvector.size()-1]; //In this case what we get is a copy of whats there in the vector;
//do some rigourous processing on this intA object and then i wish to free the memory...
delete intA;
intvector.pop_back();
}
return 0;
}




I guess the comments inside the code explain the steps.

I accept I was wrong earlier in my words ... it sounded different in my head from what i struck on the keyboard.

Well, again, if anyone thinks I need to correct myself here ... DO REPLY BACK :) ...

Thanks a lot.
Exterminator.

HighCommander4
May 5th, 2005, 05:17 PM
I'm sorry but I don't understand your second way of clearing the vector? Why are you calling delete on each element when you actually called delete on the original objects when you populated the vector? The vector performs its own memory management, and when it stores objects (like in your example), it allocates and deallocates memory for those objects automatically. So what you really are doing when you populate the vector is create a new-ed object, push a copy of it onto the vector then delete the original. Managing the copy is done by the vector itself, and you don't need to worry about it: you deleted the original object. So in your last loop you are actually causing delete to be called twice on the object: once by you and once by the vector, which of course is undefined behaviour.

The simplest way to clear the vector in your case is to call clear():

intvector.clear();

This is equivalent to (and most likely more efficient than) calling pop_back() for each element in a loop. You don't need to worry about deleting anything because the vector stores objects, not pointers.

exterminator
May 9th, 2005, 02:11 AM
Why are you calling delete on each element when you actually called delete on the original objects when you populated the vector?

I just wished to show some wrong usage to explain that vectors do keep copies of objects being pushed into them. Just wanted to show how we can make mistakes .. i guess i might not have been able to give a very sound example in this case but i have seen people do this.. they get object out the vector into some temp object and then kill the temp one and remove the member from the vector once they are done (no clearing of the whole vector coz they might use the rest of the members for some other processing or something) and this leave them with the memory mess-ups.

Anyways .. i guess the topics closed now :) !

Cheers,
Exterminator

d4m
May 9th, 2005, 08:17 AM
Alright.

Cleaning the entire vector I got.

Lets say there is 20 elements in the vector. All of it Dynamically allocated but at different times during the run.

I want to delete the element with index 5 from the vector.



vector<MyClass*> MypClassVector

delete MypClassVector[5]; <--- simple as that?

Or do I need to physically pop off each member before I can free the memory?

Another question, since I am allocating the pobjects themselves as new and not the members of the class as new, do I need to worry about making a copy constructor?

When passing the vector to a function, how should I pass it? Does it operate like typical vars as I can pass it by value, reference or pointer?

Axter
May 9th, 2005, 09:01 AM
If you want to create a vector of abstract based pointers, I recommend you use a smart pointer like that in the following link:

http://code.axter.com/arr_ptr.h

The above smart pointer is specifically designed to work with an abstract pointer being used in a STL container like std::vector.

The header has example code, and a link to an example program.

By using a smart pointer like arr_ptr, you don't have to worry about deleteing the pointer, because the clean will be done automatically.

jlou
May 9th, 2005, 11:25 AM
Or do I need to physically pop off each member before I can free the memory?That will work, but then you will have a pointer to deleted memory in your vector. It is probably better to remove the deleted pointer from the vector to ensure that you don't try to use it again later.

Another question, since I am allocating the pobjects themselves as new and not the members of the class as new, do I need to worry about making a copy constructor?Regardless of how you allocate space for your objects, you should always look at your classes to determine whether they require a custom copy constructor, assignment operator, or destructor. It depends on you class and how you intend for it to behave. For example, if it has dynamically allocated memory that you want copied when the object is copied, then you must provide your own copy constructor, copy assignment operator, and destructor (or stop using dynamically allocated memory in favor of copyable smart pointers or containers).

When passing the vector to a function, how should I pass it? Does it operate like typical vars as I can pass it by value, reference or pointer?It is like a regular object. Generally, passing by const reference is appropriate so that you don't copy the entire vector as you would passing by value. If you need to modify the vector in the function, you can pass by non-const reference instead.

d4m
May 9th, 2005, 12:08 PM
Thank you Jlou.

Axter: Since I'm still learning the concept of pointers, I really wanted to do the allocating and deallocating by hand in this version. Figured it would be a good exercise.

HighCommander4
May 9th, 2005, 06:26 PM
When passing the vector to a function, how should I pass it? Does it operate like typical vars as I can pass it by value, reference or pointer?

Always prefer passing by reference (or pointer, though references are safer). In your case, not only because of efficiency, but because the vector holds pointers and if you pass it by value, you'll end up having two vectors, each storing the same set of pointers to the same memory. You might accidentally end up deleting both stes of pointers, in which case you'd be deleting each object twice, which is undefined behaviour.

If you use a smart pointer though, you don't need to worry about that. You can safely copy the vector by value because the smart pointer's copy constructor (which the vector's copy constructor invokes) will cause a new copy to be made of the actual object too, not just the pointer. (Actually, some smart pointers use reference counting, which means they don't make a new copy of the object every time the pointer is copied, they just maintain a list of references to the object, and add a reference to that list each time the pointer is copied. The object is only deleted when the last reference goes out of scope. But you shouldn't need to worry about whether the smart pointer class you use uses reference counting or actually copies the objects. It doesn't change the behaviour in any way, you can still safely copy a vector of such pointers, etc.)

Axter
May 9th, 2005, 09:09 PM
(Actually, some smart pointers use reference counting, which means they don't make a new copy of the object every time the pointer is copied, they just maintain a list of references to the object, and add a reference to that list each time the pointer is copied. The object is only deleted when the last reference goes out of scope. But you shouldn't need to worry about whether the smart pointer class you use uses reference counting or actually copies the objects. It doesn't change the behaviour in any way, you can still safely copy a vector of such pointers, etc.)
Actually, this is an important issue, and it does change the behavior in a major way.

Consider the following code:

class base

{

public:

virtual int GetQty()=0;

virtual void increment()=0;

virtual ~base(){}

};

class D1 : public base

{

public:

D1(int i):m_i(i){}

int GetQty(){return m_i;}

void increment(){m_i+=1;}

base* clone(){return new D1(m_i);}

private:

int m_i;

};

template<class T>

void somefunction(T& arr_of_ptrs)

{

int i;

for(i = 0;i < 5;++i)

arr_of_ptrs.push_back(new D1(i));

arr_of_ptrs[1] = arr_of_ptrs[2];

arr_of_ptrs[1]->increment();

for(i = 0;i < arr_of_ptrs.size();++i)

cout << arr_of_ptrs[i]->GetQty()<< endl;;

}

void main(void)

{

cout << endl << endl << "None reference pointers....." << endl;

vector<arr_ptr<base> > none_reference_ptr;

somefunction(none_reference_ptr);

cout << endl << endl << "Reference pointers....." << endl;

vector<auto_ref_ptr<base> > reference_ptr;

somefunction(reference_ptr);



See following links for smart pointer classes:
http://code.axter.com/auto_ref_ptr.h
http://code.axter.com/arr_ptr.h

If you run the above code, you'll get different results using auto_ref_ptr compare to using arr_ptr.
The auto_ref_ptr class is a reference pointer, and the arr_ptr class is a clone type pointer.

Since a reference pointer shares it's pointer, if you assign the pointer from one part of the array to another, then you have multiple sections of your array pointing to the same data, and if you change the data in one position, you automatically change the other position(s).

So if you need an array of unique elements, and you don't want those elements sharing the same data, then you want to avoid using reference pointers.

NMTop40
May 10th, 2005, 05:07 AM
A better way to implement that is to make a copy only when you are going to write.

This can be done with smart reference-counted pointers as follows:

- Have different methods to return a const pointer (or reference) and a non-const pointer (or reference). Do not rely on const-related overloads. For example, you can have functions get_const_ptr() and get_nonconst_ptr() and get_const_ref() and get_nonconst_ref() (or shorten these names, eg ptrc(), ptrnc(), refc() and refnc() ). You might wish to implement operators ->() and operator *() but these would be one implementation only (probably const).

- Clone only when reference count is 2 or greater, and a non-const referencing method has been called.

This way you avoid unnecessary copying of objects when the vector is, for example, shifting elements for extra allocation, when objects are being placed into the vector and extracted off it for const-purpose only.

If you want to get an element in the vector to modify it, you have to be careful. You may find clone into your pointer and the modifications will not be shown up in the vector. You will have to ensure you get a reference to the smart-pointer from your vector. All-in-all, the cloning or copy-to-write smart-pointer is like a separate instance of an object, thus copying produce a different copy. But copy-to-write is used for optimisation purposes.

Axter
May 10th, 2005, 08:16 AM
A better way to implement that is to make a copy only when you are going to write.

This can be done with smart reference-counted pointers as follows:

- Have different methods to return a const pointer (or reference) and a non-const pointer (or reference). Do not rely on const-related overloads. For example, you can have functions get_const_ptr() and get_nonconst_ptr() and get_const_ref() and get_nonconst_ref() (or shorten these names, eg ptrc(), ptrnc(), refc() and refnc() ). You might wish to implement operators ->() and operator *() but these would be one implementation only (probably const).

- Clone only when reference count is 2 or greater, and a non-const referencing method has been called.

This way you avoid unnecessary copying of objects when the vector is, for example, shifting elements for extra allocation, when objects are being placed into the vector and extracted off it for const-purpose only.

If you want to get an element in the vector to modify it, you have to be careful. You may find clone into your pointer and the modifications will not be shown up in the vector. You will have to ensure you get a reference to the smart-pointer from your vector. All-in-all, the cloning or copy-to-write smart-pointer is like a separate instance of an object, thus copying produce a different copy. But copy-to-write is used for optimisation purposes.
I'm actually currently working on this type of smart pointer.
I'm currently calling it lazy_ptr for lack of a better name.
It will be a combination of the reference counting pointer, and the arr_ptr(clone).
However, I'm creating two sets of operator-> and operator* methods, like the following:
T* operator->();
const T* operator-> const();

When and if the non-constant method is called, I will clone the object if it has 2 or more references, and then have the pointer pointing to the new clone.
If the constant method is call, it will continue accessing the referenced pointer.
That way it only clones itself when needed automatically.

This logic is similar to the logic used for std::string reference counting.

NMTop40
May 10th, 2005, 08:41 AM
const overloads do not really work for smart-pointers. They are an illusion.

Let's have a reference-counted smart-pointer. I will call it SharedPtr<T> for now. This applies to boost as well as to any you and I are likely to implement.

Now, somebody I have const SharedPtr<T> &. So you might think I can only call const methods on my T. I think not. What I have is actually comparable to a T* const, not a const T*.

Why? Because T is const within the pointer and not what it points to.

And suppose I invoke a simple copy-constructor.


const SharedPtr<T> & spt1;
SharedPtr<T> spt2( spt1 ); // perfectly legal, normal copy constructor


So now I have suddenly created a brand new SharedPtr, pointing to the same T but clearly this one is not const.

If I want a shared pointer to a const T then I must use


SharedPtr< const T >
.

Incidentally, do your smart pointers have templated copy-constructors?


template <typename T2>
SharedPtr<T>( const SharedPtr< T2 > & rhs );


(Note, you should also specialise it for your own T because the compiler may pick the default as the best match, not the templated one).

This should be implemented to create a SharedPtr<T> from a SharedPtr<T2> when and only when you can automatically cast from T2 to T, i.e. when T is const T2 or when T is a base class of T2.

(Another reason perhaps to use boost and not try to write your own, but it is instructive to go through these points).

So now back to the earlier point. When we are going to modify our T we want to make a copy. But your overload is based on having a const SharedPtr<T> not a SharedPtr< const T >. (How would your template know if it had a const argument anyway?).

In addition, it could well be that we have a non-const T pointer, but are only going to call const methods on it. Nothing wrong with that and we don't want to have to const_cast it to const (you can use const_cast that way too) just to get it to call the right overload. (Note that std::map has that effect too with operator[], and it can be messy if you want to look up that way without creating a new element, so it is better not to use the const-overload of std::map::operator[]).

Basically, we can rely on our client code using the correct function to get a non-const reference/pointer when it is going to modify, and a const reference/pointer when it is not. When it calls the non-const reference/pointer method we can then clone if necessary. The compiler will trap any occasion where you call the const method then try to modify.

By the way, operator* on a SmartPtr<T> usually returns a T& (or a const T&). It is an invalid operator when the pointer is NULL. And you might wish to handle the NULL case better.

d4m
May 10th, 2005, 09:39 AM
Hmm... do I grumble or not... lol

I appreciate all the help, but everyone keeps saying use the smart pointers and I really don't want to. I know they are better to use. I know what they can do and if I knew enough about pointers in general and felt comfortbale with them, I would use the smart pointers in a heart beat.

Probably once I get this working via non-smart pointers, then I will move over to smart pointers. This program is as much of a school project as it is a crash course in C++ for me being an EE.

Just need to maintain a dynamic database of objects derived from a base class. Just need to insert objects or delete a single object from the vector. Everything else is just math routines, file i/o and printing routines to the console.

Axter
May 10th, 2005, 09:52 AM
const overloads do not really work for smart-pointers. They are an illusion.

Let's have a reference-counted smart-pointer. I will call it SharedPtr<T> for now. This applies to boost as well as to any you and I are likely to implement.

Now, somebody I have const SharedPtr<T> &. So you might think I can only call const methods on my T. I think not. What I have is actually comparable to a T* const, not a const T*.

Why? Because T is const within the pointer and not what it points to.

And suppose I invoke a simple copy-constructor.


const SharedPtr<T> & spt1;
SharedPtr<T> spt2( spt1 ); // perfectly legal, normal copy constructor


So now I have suddenly created a brand new SharedPtr, pointing to the same T but clearly this one is not const.

If I want a shared pointer to a const T then I must use


SharedPtr< const T >
.

Incidentally, do your smart pointers have templated copy-constructors?


template <typename T2>
SharedPtr<T>( const SharedPtr< T2 > & rhs );


(Note, you should also specialise it for your own T because the compiler may pick the default as the best match, not the templated one).

This should be implemented to create a SharedPtr<T> from a SharedPtr<T2> when and only when you can automatically cast from T2 to T, i.e. when T is const T2 or when T is a base class of T2.

(Another reason perhaps to use boost and not try to write your own, but it is instructive to go through these points).

So now back to the earlier point. When we are going to modify our T we want to make a copy. But your overload is based on having a const SharedPtr<T> not a SharedPtr< const T >. (How would your template know if it had a const argument anyway?).

In addition, it could well be that we have a non-const T pointer, but are only going to call const methods on it. Nothing wrong with that and we don't want to have to const_cast it to const (you can use const_cast that way too) just to get it to call the right overload. (Note that std::map has that effect too with operator[], and it can be messy if you want to look up that way without creating a new element, so it is better not to use the const-overload of std::map::operator[]).

Basically, we can rely on our client code using the correct function to get a non-const reference/pointer when it is going to modify, and a const reference/pointer when it is not. When it calls the non-const reference/pointer method we can then clone if necessary. The compiler will trap any occasion where you call the const method then try to modify.

By the way, operator* on a SmartPtr<T> usually returns a T& (or a const T&). It is an invalid operator when the pointer is NULL. And you might wish to handle the NULL case better.
You're confusing the implementation.
The implementation will not create a type const SmartPtr<T>, nor does it use that concept for determining if a write access is perform or a read access is performed.
The concepted is based on the method type, and the compiler handles this automatically.
If the T method type is const, then the constant operator is called, and if the T method type is non-constant, then the non-constant type is called.
It's not that complicated, and I've already run some test version that work.

The following is a partial working version of the code. I have not completed the implementation, but if you run it just using -> operator, you'll see that it functions as I've described.

// lazy_ptr.h : header file for class lazy_ptr
//
#if !defined(lazy_ptr_H__HEADER_GUARD_)
#define lazy_ptr_H__HEADER_GUARD_ 1
template<class T>
class lazy_ptr
{
private:
class func_ptr_interface
{
public:
virtual T* clone_t() = 0;
virtual ~func_ptr_interface(){}
virtual func_ptr_interface* clone_func_ptr_interface(T* Obj)=0;
};
template<typename T_obj, typename F>
class func_ptr_holder : public func_ptr_interface
{
public:
typedef F T_func;
inline func_ptr_holder(T_obj &Obj, T_func Func): m_Obj(Obj), m_Func(Func){}
inline T* clone_t(){return (m_Obj.*m_Func)();}
inline func_ptr_interface* clone_func_ptr_interface(T* Obj)
{//dynamic_cast should be used in follow code, but that would require RTTI, and would make the code less efficient
return new func_ptr_holder<T_obj, F>(*(static_cast<T_obj*>(Obj)), m_Func);
}
private:
T_func m_Func;
T_obj &m_Obj;
};
template<typename T_obj, typename F>
inline func_ptr_interface* create_func_ptr_interface(T_obj* type, F func){
return new func_ptr_holder<T_obj, F>(*type, func);
}
public:
template<typename T_obj>
lazy_ptr(T_obj* type):m_RefCounter(new RefCounter(type)), m_func_ptr_interface(create_func_ptr_interface(type, T_obj::clone)){}
lazy_ptr(const lazy_ptr& Src):m_RefCounter(Src.m_RefCounter), m_func_ptr_interface(Src.m_func_ptr_interface)
{Src.m_RefCounter->Increment();}
virtual ~lazy_ptr()
{
if (!m_RefCounter->Decrement())
{
delete m_RefCounter;
delete m_func_ptr_interface;
}
}
inline int get_ref_count() const {return m_RefCounter->get_ref_count();}
//Operators
inline const T* operator->() const {return m_RefCounter->get();}
inline T* operator->()
{
if (m_RefCounter->get_ref_count() < 2)
return m_RefCounter->get();
RefCounter *Tmp_RefCounter = new RefCounter(m_func_ptr_interface->clone_t());
m_RefCounter->Decrement();
m_func_ptr_interface = m_func_ptr_interface->clone_func_ptr_interface(m_RefCounter->get());
m_RefCounter = Tmp_RefCounter;
return m_RefCounter->get();
}
inline T& operator*() const{return *m_RefCounter->get();}
lazy_ptr& operator=(const lazy_ptr&Src) {
if (this != &Src && m_RefCounter->get() != Src.m_RefCounter->get()) {
if (!m_RefCounter->Decrement())
{
delete m_RefCounter;
delete m_func_ptr_interface;
}
m_RefCounter->Increment();
m_RefCounter = Src.m_RefCounter;
m_func_ptr_interface = Src.m_func_ptr_interface;
}
return *this;
}
private:
//Block usage of following operators
bool operator==(const lazy_ptr&);
bool operator!=(const lazy_ptr&);
class RefCounter
{
public:
RefCounter(T* type):m_type(type), m_Counter(1){}
~RefCounter(){delete m_type;m_type=NULL;}
inline void Increment()const{++m_Counter;}
inline bool Decrement()const{--m_Counter;return (m_Counter > 0);}
inline int get_ref_count() const{return m_Counter;}
inline T* get() const{return m_type;}
private:
T* m_type;
mutable int m_Counter;
};
protected:
//Only data member for lazy_ptr
RefCounter *m_RefCounter;
func_ptr_interface *m_func_ptr_interface;
};
#endif // !defined(lazy_ptr_H__HEADER_GUARD_)


You can use the following test app to test above code.


#include "lazy_ptr.h"
#include <stdlib.h>
#include <iostream>
#include <vector>
using namespace std;

class base
{
public:
virtual int GetQty()const=0;
virtual void increment()=0;
virtual ~base(){}
};
class D1 : public base
{
public:
D1(int i):m_i(i){cout << "D1 constructor" << endl;}
int GetQty()const{return m_i;}
void increment(){m_i+=1;}
base* clone(){return new D1(m_i);}
~D1(){cout << "D1 destructor" << endl;}
private:
int m_i;
};
void somefunction()
{
vector<lazy_ptr<base> > arr_of_ptrs;
arr_of_ptrs.push_back(new D1(1));
unsigned i;
for(i = 0;i < 5;++i)
arr_of_ptrs.push_back(arr_of_ptrs[0]);
arr_of_ptrs[1]->increment();
for(i = 0;i < arr_of_ptrs.size();++i)
cout << arr_of_ptrs[i]->GetQty()<< endl;;
}
int main(int argc, char* argv[])
{
somefunction();
system("pause");
return 0;
}


You make a good point about template copy constructors.
I'll have to look into this some more.

I'm not a fan of boost classes.
I don't like that most of them are too dependent on the entire boost library, and it's hard to pull one class, without having to pull in a bunch of other files.
IMHO, that makes them less reusable.

IMHO, if I want a class foofoo, then all I should need is foofoo.h
If I have to include half the boost library to use foofoo, then it's not worth my while to use foofoo.
That's why the generic classes I built are small, and do not have dependencies on non-standard headers.

NMTop40
May 10th, 2005, 10:05 AM
If you do not want to use smart-pointers then you have to delete the pointer as you remove it from the vector.

If you are populating your vector once, then deleting everything only at the end, then you can implement this in one place - possibly in the destructor of the class that stores the vector.

One way is:

while ( !v.empty() )
{
delete v.back();
v.pop_back();
};


Another way would be to use a deleter so

template <typename T >
default_deleter< T >
{
public:
void operator() ( const T* t )
{
delete t;
}
};

std::for_each( v.begin(), v.end(), default_deleter<T>() );


This sort of thing is done in advanced programming where you want to customise your deleter (and note std::allocator() ). The obvious disadvantages of doing it the latter way are (1) you have to write default_deleter somewhere if you don't have it already and (2) after the std::for_each you are left with a vector of dangling pointers. The first way you were removing the pointers from the vector the moment you deleted them.

You should consider using smart-pointers, because they are much easier to use, albeit rather difficult to write well. (They took a while to perfect the boost one, involving several peer-reviews by different experienced software engineers).

NMTop40
May 10th, 2005, 10:24 AM
You're confusing the implementation.
The implementation will not create a type const SmartPtr<T>, nor does it use that concept for determining if a write access is perform or a read access is performed.
The concepted is based on the method type, and the compiler handles this automatically.
If the T method type is const, then the constant operator is called, and if the T method type is non-constant, then the non-constant type is called.
It's not that complicated, and I've already run some test version that work.

Are you sure?


// lazy_ptr.h : header file for class lazy_ptr
//
#if !defined(lazy_ptr_H__HEADER_GUARD_)
#define lazy_ptr_H__HEADER_GUARD_ 1

template<class T>
class lazy_ptr
{
inline const T* operator->() const {return m_RefCounter->get();}
inline T* operator->()
{
// etc.
}
};
#endif // !defined(lazy_ptr_H__HEADER_GUARD_)


This is the crucial part of your code. The difference between the overloads is that the first is a const function and the second a non-const function. The significant "const" here is the one that appears on the line after operator->() and not in the return type.;


#include "lazy_ptr.h"
#include <stdlib.h>
#include <iostream>
#include <vector>
using namespace std;

class base
{
public:
virtual int GetQty()const=0;
virtual void increment()=0;
virtual ~base(){}
};
class D1 : public base
{
public:
D1(int i):m_i(i){cout << "D1 constructor" << endl;}
int GetQty()const{return m_i;}
void increment(){m_i+=1;}
base* clone(){cout << "Cloning D1 with " << m_i << endl; return new D1(m_i);}
~D1(){cout << "D1 destructor" << endl;}
private:
int m_i;
};
void somefunction()
{
vector<lazy_ptr<base> > arr_of_ptrs;
arr_of_ptrs.push_back(new D1(1));
unsigned i;
for(i = 0;i < 5;++i)
arr_of_ptrs.push_back(arr_of_ptrs[0]);
arr_of_ptrs[1]->increment();
for(i = 0;i < arr_of_ptrs.size();++i)
cout << arr_of_ptrs[i]->GetQty()<< endl;;
}
int main(int argc, char* argv[])
{
somefunction();
system("pause");
return 0;
}


Try the test now with my amendment of your clone() function to output a cout (so you know when you are duplicating). You think you should be cloning just once. I would expect 4 more.

Bob Davis
May 10th, 2005, 10:39 AM
This program is as much of a school project as it is a crash course in C++ for me being an EE.

Ironically enough, I can tell you from experience that 99% of EEs would do anything to keep from having to write software, much less use anything more advanced than C. :) It's tough being in the other 1%.

Axter
May 10th, 2005, 10:52 AM
Are you sure?


// lazy_ptr.h : header file for class lazy_ptr
//
#if !defined(lazy_ptr_H__HEADER_GUARD_)
#define lazy_ptr_H__HEADER_GUARD_ 1

template<class T>
class lazy_ptr
{
inline const T* operator->() const {return m_RefCounter->get();}
inline T* operator->()
{
// etc.
}
};
#endif // !defined(lazy_ptr_H__HEADER_GUARD_)


This is the crucial part of your code. The difference between the overloads is that the first is a const function and the second a non-const function. The significant "const" here is the one that appears on the line after operator->() and not in the return type.;


#include "lazy_ptr.h"
#include <stdlib.h>
#include <iostream>
#include <vector>
using namespace std;

class base
{
public:
virtual int GetQty()const=0;
virtual void increment()=0;
virtual ~base(){}
};
class D1 : public base
{
public:
D1(int i):m_i(i){cout << "D1 constructor" << endl;}
int GetQty()const{return m_i;}
void increment(){m_i+=1;}
base* clone(){cout << "Cloning D1 with " << m_i << endl; return new D1(m_i);}
~D1(){cout << "D1 destructor" << endl;}
private:
int m_i;
};
void somefunction()
{
vector<lazy_ptr<base> > arr_of_ptrs;
arr_of_ptrs.push_back(new D1(1));
unsigned i;
for(i = 0;i < 5;++i)
arr_of_ptrs.push_back(arr_of_ptrs[0]);
arr_of_ptrs[1]->increment();
for(i = 0;i < arr_of_ptrs.size();++i)
cout << arr_of_ptrs[i]->GetQty()<< endl;;
}
int main(int argc, char* argv[])
{
somefunction();
system("pause");
return 0;
}


Try the test now with my amendment of your clone() function to output a cout (so you know when you are duplicating). You think you should be cloning just once. I would expect 4 more.
It looks like you're right.
I'm going to have to rethink this.

NMTop40
May 10th, 2005, 10:56 AM
Simple. 4 methods, as I suggested.

ptr_c(), ptr_nc() ref_c() and ref_nc() (or use your own naming convention)

ptr_c() and ref_c() are functions to obtain const pointer/ref and do not clone
ptr_nc() and ref_nc() are functions to obtain non-const pointer/ref and clone if reference count > 1

Overload operator->() and operator*() if you want but make them equivalent to the const functions.

HighCommander4
May 10th, 2005, 04:58 PM
A better way to implement that is to make a copy only when you are going to write.

This can be done with smart reference-counted pointers as follows:

- Have different methods to return a const pointer (or reference) and a non-const pointer (or reference). Do not rely on const-related overloads. For example, you can have functions get_const_ptr() and get_nonconst_ptr() and get_const_ref() and get_nonconst_ref() (or shorten these names, eg ptrc(), ptrnc(), refc() and refnc() ). You might wish to implement operators ->() and operator *() but these would be one implementation only (probably const).

- Clone only when reference count is 2 or greater, and a non-const referencing method has been called.

This way you avoid unnecessary copying of objects when the vector is, for example, shifting elements for extra allocation, when objects are being placed into the vector and extracted off it for const-purpose only.

If you want to get an element in the vector to modify it, you have to be careful. You may find clone into your pointer and the modifications will not be shown up in the vector. You will have to ensure you get a reference to the smart-pointer from your vector. All-in-all, the cloning or copy-to-write smart-pointer is like a separate instance of an object, thus copying produce a different copy. But copy-to-write is used for optimisation purposes.

I thought that was the default behaviour of reference-counted pointers? Just like reference counted implementations of the string class?

Axter
May 11th, 2005, 08:44 AM
I thought that was the default behaviour of reference-counted pointers? Just like reference counted implementations of the string class?
No, they work differently.

A reference counting pointer always shares the same pointer.

Where-as a reference counting string shares the same data, until one of the sharing objects tries to modify it.



I thought making a reference counting pointer using similar logic to the reference counting string would be easy to implement.

But it turns out, it's much more complex then I originally had anticipated.




It’s easy for an std::string object to determine when you’re accessing a method that can modify the object.

It’s much more difficult for a smart pointer to detect when a call is being made to a method that can modify the object (as NMTop40 has very correctly pointed out to me).



You could use methods like NMTop40 suggested, but this is less transparent as an interface, and requires explicit calls.



I was looking to avoid this type of explicit interface, by making a smart pointer that could detect constant and non-constant method calls, but that does not seem possible.

Axter
May 11th, 2005, 09:49 AM
Simple. 4 methods, as I suggested.

ptr_c(), ptr_nc() ref_c() and ref_nc() (or use your own naming convention)

ptr_c() and ref_c() are functions to obtain const pointer/ref and do not clone
ptr_nc() and ref_nc() are functions to obtain non-const pointer/ref and clone if reference count > 1

Overload operator->() and operator*() if you want but make them equivalent to the const functions.
I'm thinking of doing something similar. Where the operator->() and *() methods have one set of behavior, and a explicit function call has another set of behavior.
However, I'm considering doing the reverse, where the operators will have none-constant access, and explicit function has constant access.

This would basically have the smart pointer work like a clone pointer by default, but if the explicit constant access function is used, then it can work as a reference counting smart pointer.

This is not what I really wanted, but since I've already got half the code done, I figure I finish it, and maybe it could be of some very limited use.

It can still work as a lazy pointer, if you're passing it to a function that might need it's own copy of the object, but only under conditional logic.
That would make it efficient in that a local copy of the object is not create until it's certain that it needs a local copy.

NMTop40
May 11th, 2005, 09:58 AM
I suggested having the operator overload as const because it is the one you are more likely to call "without thinking", and therefore would be more likely to lead to a programmer cloning by mistake. After all, it would not fail to compile if you used this to call a const method, whereas making the operator return the const pointer, it would fail to compile if you thoughtlessly used the operator and tried to call a non-const method.

NMTop40
May 11th, 2005, 11:10 AM
I might ask you how often you want to clone anyway. I cannot think of many occasions I have wanted to clone an abstract object. I pretty much never copy-construct anything, in fact, except for function objects (and smart-pointers).

If you are cloning an abstract object, ask yourself whether you should:

- be using a plain reference-counted shared-pointer (they are pointing to the same object).
- Have a concrete class that has a reference-counted shared-pointer to an abstract class, and use that for your abstraction. (Is this another adapter?)

Suppose, for example, I have an abstract collection of objects called Coll and I want an iterator to be able to run through the collection.

Now your iterator would probably have a pointer to Coll and some either item relating to its current position. In order to get to the data, or to advance, your iterator would have to consult Coll and pass it the positioning data too.

You must consider the fact that the positioning data itself could be dependent on the implementation of Coll, and therefore consider that you actually have an abstract class, let's call it Pos. We can now use this abstract class Pos to iterate through our data.

But what about our iterator? Easy - our iterator will have a shared-pointer to a Pos and will use this Pos to access data and to increment itself, and to compare itself to another Pos. But iterator itself (which is going to be cloned) is now a concrete class.

Axter
May 11th, 2005, 11:28 AM
I might ask you how often you want to clone anyway. I cannot think of many occasions I have wanted to clone an abstract object. I pretty much never copy-construct anything, in fact, except for function objects (and smart-pointers).

If you are cloning an abstract object, ask yourself whether you should:

- be using a plain reference-counted shared-pointer (they are pointing to the same object).
- Have a concrete class that has a reference-counted shared-pointer to an abstract class, and use that for your abstraction. (Is this another adapter?)

Suppose, for example, I have an abstract collection of objects called Coll and I want an iterator to be able to run through the collection.

Now your iterator would probably have a pointer to Coll and some either item relating to its current position. In order to get to the data, or to advance, your iterator would have to consult Coll and pass it the positioning data too.

You must consider the fact that the positioning data itself could be dependent on the implementation of Coll, and therefore consider that you actually have an abstract class, let's call it Pos. We can now use this abstract class Pos to iterate through our data.

But what about our iterator? Easy - our iterator will have a shared-pointer to a Pos and will use this Pos to access data and to increment itself, and to compare itself to another Pos. But iterator itself (which is going to be cloned) is now a concrete class.The idea would be similar comparison to using an std::string.
A function that does NOT need a local modifiable copy of a std::string can have the following signature:
void func(const std::string &);
In cases like this, the function will do just fine with the source copy of the string.
Similar logic with the lazy_ptr
void func(const lazy_ptr<foo>&);

So in above code, we expect that the using code doesn't need a local modifiable copy of the pointer.

If you have a function that needs a local copy of the string (and not original), then you would have a signature like this:
void func(std::string src);
If you need a local copy of the string which you want to modify, you gain no benifit by having a constant reference argument.
One nice thing about a reference string, is that if you pass by value, it does not create a new string, and instead references the source string.
So if your function only needs to modify the input arguments under certain conditions, you can still gain the benifit of not having to create a completely new string object if the condition is false.

Similar logic could be apply to a lazy_ptr.
void func(lazy_ptr<foo> src);

The above function is passing by value, but a new foo object is not create until non-constant access is made.
So you could have some conditional logic that if false, would not require modification of the src object, and therefore it would not require a new object to be created.

Hence the name, lazy pointer, since it only recreates the object when needed, and not absolutely.

The point is to try and duplicate the logic you would use in a string reference counter.