Advertisement

cow pointers

Started by September 09, 2000 05:33 PM
12 comments, last by null_pointer 24 years, 3 months ago
After responding to Dire.Wolf's post, I was inspired to write a small library of smart handles (like smart pointers), and had a neat idea - "deallocators" Specifically: template &lttypename type> struct heap { void deallocate(type* resource) { delete( resource ); } }; template &lttypename type> struct heap_array { void deallocate(type* resource) { delete[]( resource ); } }; Since the smart handles take a template parameter to indicate which resource deallocator to use when the handle deallocates something, you can use any resource type you like, like a Windows HANDLE or whatever else. Also: int main(int, char*) { handle&ltmyclass, heap&ltmyclass> > p1(new myclass); handle&ltmyclass, heap_array&ltmyclass> > p2(new myclass[10]); // deallocators take care of everything when p1 and p2 go out of scope } It'll cut a ton of debugging time out of the library I'm writing, and I need to write these smart handles to use with STL containers anyway. Unfortunately, I don't know of a way to implement a "copier" - a function object that copies objects. If the smart handle classes don't know whether the resource is an array or just a single object, how does the copier get this information? All copy functions must have the same syntax, and have the same return type... The syntax must be the same for arrays as well as single objects. Thanks for your help! - null_pointer Sabre Multimedia Edited by - null_pointer on 9/9/00 5:36:29 PM
quote:
If the smart handle classes don''t know whether the resource is an array or just a single object, how does the copier get this information?


Maybe I''m missing something here, but why does the copier need to know if it''s an array or not? Can''t you use the copy ctor of the type your holding? So if heap and heap_array both have a copy ctor you wouldn''t need to define on for your handle class. The compiler would just call the copy ctor for each of the members.
Advertisement
No, heap and heap_array don''t hold the data, they just deallocate it. So, how will the copy function know whether to allocate an array (and what size?)? I wanted to get around using virtual functions and make the code all inlined so there is virtually no speed decrease (bad pun).


- null_pointer
Sabre Multimedia
I've just realised what you mean now, I thought heap and heap_array where scoped pointers. But there's just used as a parameter to the handle class, kinda like std::allocator.

What does it mean to copy a handle class? Should it copy the handle, or share the handle with the other object.

If you just need to share the handle, adding a ref count to heap and heap_array would solve that problem. If you need to copy the object being handled, I can't think of a way to do it without passing in the size to one of the objects. To keep it as generic as possible, you could use another template class that does the copying.

template&lttypename Type>struct copy_resource{	static Type* copy(const Type* src) const	{		return new Type(src);	}}; 


Then you could defined heap like this:

template &lttypename type, typename copier = copy_resource&lttype> >struct heap : copier{ void deallocate(type* resource) { delete( resource ); } }; 


The reason for the inheritance is so that handle just calls whatever::copy(src), that way you can use a static function (there's no instance date) so it'll keep the handle class small (deallocate could also be static). You could change it to a private base class then add a using declaration if you like, as long as heap exposes a copy it I'll work.

For heap_handle you'll need a way for it to know the size of the thing it's holding, so something like this should work (although it means the size needs to be know at compile time)

template&lttypename Type, size_t Size>struct copy_resource_array{	static Type* copy(const Type* src) const	{		Type* temp = new Type[Size];		std::copy(src, src+Size, temp);		return temp;	}}; 


The problem with this is that you need to know the size at compile time, which you probably wont. But unless you give clients access to the ctor of heap_array somehow I can't think of another way to do this.

If you don't want to pass a copier to heap_array when you don't need to copy it, you could create another class which will generate a compile time error when you try to copy it:

template&lttypename Type>struct cant_copy_resource{	static Type* copy(const Type* src) const	{		int cant_copy[-1]; //the -1 size will give you a compile error	}}; 


then make cant_copy_resource the default copier for heap_array.

Making the copier a template type lets you have more interesting coping for some types, for example a class that has a virtual clone function to clone it could be used like this

template&lttypename Type>struct prototype_resource{	static Type* copy(const Type* src) const	{		return src-&gtclone();	}}; 


This wouldn't really fit in with the rest of your handle class though, so it's probably better to go in an std::auto_ptr type class. I might have missed the point again here, which would make this a waste of time

btw is there a reason you can't use std::vector instead of heap_array?

Edited by - Wilka on September 9, 2000 8:10:32 PM
ah...no...I just solved the problem. Like you said, it was a design problem - if the user wants to use an array, why not use a standard vector or some type of collection class? It simplifies the design tremendously, and now the copy function objects work as expected!

Thanks!

BTW, I''m making several types of smart "pointer" classes, because they each have different strengths and weaknesses, and I thought it''d be better to just get the whole thing over and done with anyway. A cow pointer means "Copy On Write", and that''s how the STL string classes are implemented. Each copied object "shares" the data via a cow-type pointer class, until the data is modified, at which time a copy of the data is made and is modified. This is really easy to implement because of the difference between const and non-const functions...

You also can''t use auto_ptr as the element-type in STL container classes, depending on what version of the C++ standard the compiler supports - a reference counting pointer or something similar is needed for that.

Anyway, I''ve been babbling on enough about smart pointers today...


- null_pointer
Sabre Multimedia
Let me know how you overcome the inheritance problem in VC++

        shared_ptr<AudioMedia>    audio_media(new AudioMedia);shared_ptr<CDAudio>       cd_audio(new CDAudio);audio_media = cd_audio;        


VC++6 does not seem to like this. The only way around it, as far as I can tell, is to declare an external template function or make the reference count and data pointer public - or use a more compliant compiler

- Dire Wolf
direwolf@digitalfiends.com

*** If you don't mind, can you keep me informed of your progress with the problem listed above (if you are using VC++6).

Thanks.

Edited by - Dire.Wolf on September 9, 2000 10:27:14 PM
[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com
Advertisement
quote: Original post by Dire.Wolf

Let me know how you overcome the inheritance problem in VC++


Easy - just switch to a company that is interested in making their compiler better, like Cygwin.

Hey! Since you can set up the Intel compiler in VC's IDE, does anyone know how to set up Borland's free command-line compiler? Or are they incompatible?

For people using these classes in VC, I'll just place the templated member stuff and friend declaration inside an #ifdef/#endif pair, and let people complain to MS about the problems.

BTW, VC chokes on this line, claiming an "int" type redefinition, which IIRC int is what it uses to test the templates' syntax before actually allowing you to use them:


template friend class smart_handle::operator=(const smart_handle& other_smart_handle);



Later:

HA! I beat MS's compiler! Now to figure out the linker errors...



- null_pointer
Sabre Multimedia


Edited by - null_pointer on September 10, 2000 8:51:21 AM
ah! Now the question is whether or not to allow handles with different deallocator types to be assigned to each other:


automatic::smart_handle p1(new base);
automatic::smart_handle p2(new derived);

p1 = p2; // error! heap != heap



Now I changed the heap from a templated struct to a normal struct with a templated deallocate function. Same goes for the duplicators.

However, a new problem has arisen. Since the duplicators use templated functions, they can''t tell the difference between a base class and a derived class, if the copy_handle class contains only a base class pointer:


template &lttypename type, typename duplicator_type = heap, typename deallocator_type = heap>
class copy_handle
{
// public functions...

private:
void copy(); // called to make a duplicate
smart_handle&lttype, deallocator_type> handle; // a ref-counted handle

duplicator_type duplicator;
};

template &lttypename type, typename duplicator_type, typename deallocator_type>
void copy_handle&lttype, duplicator_type, deallocator_type>::copy()
{
// other code...

handle = smart_handle&lttype, deallocator_type>( duplicator.duplicate(handle->get()) );

// other code...
}



Here is the problem:


class base {};
class derived : public base {};

copy_handle&ltbase> p1(new base);
copy_handle&ltderived> p2(new derived);

p1 = p2; // fine

*p2 = // at the *p2, a copy is made, but it''s a base!



Since a base* is the type passed to the duplicator''s template function, a base is created as a copy of the derived, which is bad! Is there a way to allow safe duplication? Should I change the duplicators back into templated structs with normal functions and force the user of the copy_handle to come up with a solution? It''s possible - a factory-type duplicator could be used with some form of user-defined RTTI...

What do you think I should do?


- null_pointer
Sabre Multimedia
quote:
Now the question is whether or not to allow handles with different deallocator types to be assigned to each other


I''d say no, you shouldn''t be able to assign them. If you release a resource the wrong way, you get a load of problems. For example you might have some resources allocated in a dll, and you''d also need to release them in the dll. So you would create a special deallocator that handles all that stuff for you, then just use the correct deallator type when using resources from a dll.

I''ve just thought an example that''s a bit more likely (although it wont result in an access violation), you need to be sure that when you new[] something you also delete[] it. But if the deallocator isn''t fixed, you could end up calling delete on it, so you''d leave a load of un-destructed objects in memory.

quote:
However, a new problem has arisen....
Since a base* is the type passed to the duplicator''s template function, a base is created as a copy of the derived, which is bad! Is there a way to allow safe duplication?


You could force Base to have a virtual Clone() function, but that would mean all types would need one which isn''t a good idea. The most generic way I can think to do this is to create a template function which knows how to copy the type that your holding. Then the user of the class can specialize that class for any types that need to be copied a different way. Does it this means you don''t need to use RTTI since it''ll all be resolved statically. Something like this:

// default CopyType template, this will work for most typestemplate<class T>T* CopyType(const T* src){	return new T(*src); }// Specialisation for Base - uses virtual Clone() functiontemplate<>Base* CopyType<Base>(const Base* src){	return src->Clone();} 


Then to make a copy of the type, you use

type* ptr = CopyType<type>(other.ptr); 


If type is a Base, this will use the Clone() function, otherwise it''ll do a normal copy.

Something I forgot to mention would could cause you problems, template types aren''t polymorphic. So if you have this :

struct Base{};struct Mid : Base{};struct Sub : Base{}; 


And a template specialisation for Base, if you call it (the template function) with a Mid or Sub, it wont use the Base version, it''ll use the none specialised one.

This topic is closed to new replies.

Advertisement