Sign in to follow this  

Generic COW?

This topic is 3339 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I really need to add some copy-on-write functionality to this class I have, and I find myself needing to do this every once in a while. So it occurred to me to make some generic COW stuff that any class can use... Anyway I was wondering if this seemed like a good approach before modifying a very large project with it. If not, what is a better approach, and why? On a side note, doing a search on "COW" in gamedev.net turns up many many posts on actual cows, like the animal. The COW base class:
template <class T> class COWBase {
	void DecRef(int *Ref_, T *Data_) {
		if (--(*Ref_)) return;
		delete Ref_;
		delete Data_;		
	}

	void DecRef() {
		DecRef(Ref, Data);
	}

	void IncRef() {
		++(*Ref);
	}

	int *Ref;

protected:
	COWBase() : Ref(new int(1)), Data(new T()) { } 

	COWBase(const T &Value) : Ref(new int(1)), Data(new T(Value)) { }
	
	COWBase(const COWBase &Copy) : Ref(Copy.Ref), Data(Copy.Data) { IncRef(); }
	
	COWBase &operator= (const COWBase &Copy) {
		if (&Copy != this) {
			DecRef();
			Ref = Copy.Ref;
			Data = Copy.Data;
			IncRef();
		}
		return *this;
	}
	
	~COWBase() { DecRef(); }

	// The class that inherits this will use these...
	void COW() {
		T *OldData = Data;
		int *OldRef = Ref;
		Ref = new int(1);
		Data = new T(*OldData);
		DecRef(OldRef, OldData);
	}

	// More like... overwrite than copy-on-write
	void COW(const T &NewData) {
		DecRef();
		Ref = new int(1);
		Data = new T(NewData);
	}

public:
	T *Data;
};
And an example using it:
struct ImageData {
	ImageData() : Width(0), Height(0) { }
	ImageData(int Width_, int Height_) : Width(Width_), Height(Height_) { }
	int Width, Height;
};

// You would make this publicly inherited if you wanted public access to the "Data" variable
class Image : private COWBase<ImageData> {
public:
	Image() {
		Data->Width = 0;
		Data->Height = 0;
	}
	
	// If you define your own assignment op or copy constructor you will need to explicitly
	// call the COWBase version, otherwise it will take care of it for you.
	Image &operator= (const Image &Copy) {
		COWBase::operator= (Copy);
		return *this;
	}
	
	~Image() {
	}
	
	void SetWH(int W, int H) {
		COW(ImageData(W, H));
		//Data->Width = W;
		//Data->Height = H;
	}
};

So is there anything wrong with this approach as far as anyone can tell? Or is there a better one? Thanks. Cheers -Scott [Edited by - popsoftheyear on October 22, 2008 4:21:02 PM]

Share this post


Link to post
Share on other sites
Not that I'm very good at this, but wouldn't it be possible to use something like boost::shared_ptr to implement copy-on-write (and not inherit, but compose).

I added some output code:


#include <iostream>
#include <boost/shared_ptr.hpp>

struct ImageData {
ImageData() : Width(0), Height(0) { std::cout << "ImageData()\n"; }
ImageData(int Width_, int Height_) : Width(Width_), Height(Height_)
{
std::cout << "ImageData(" << Width_ << ", " << Height_ << ")\n";
}
~ImageData() { std::cout << "~ImageData()\n"; }
int Width, Height;
};

class Image {
public:
Image(): Data(new ImageData) {
}

void SetWH(int W, int H) {
if (!Data.unique()) {
Data.reset(new ImageData(W, H));
}
else {
Data->Width = W;
Data->Height = H;
}
}
void Print() const
{
std::cout << "[" << Data->Width << "," << Data->Height << "]\n";
}

private:
boost::shared_ptr<ImageData> Data;
};

int main()
{
Image i;
i.SetWH(10, 20);
Image j(i);
i.Print();
j.Print();
i.SetWH(5, 6);
i.Print();
j.Print();
}

Share this post


Link to post
Share on other sites
I played with this one briefly - it has its own counted pointer (see same site) which could probably easily be typdefed with shared_ptr. its nice in that its completely non-intrusive.

the key feature is that there are accessors defined for both const and non const operations

Quote:
const X& operator*() const throw() {return *itsPtr;}
const X* operator->() const throw() {return itsPtr.get();}
const X* get() const throw() {return itsPtr.get();}

// this stuff requires mutex protection ...
X& operator*() {copy(); return *itsPtr;}
X* operator->() {copy(); return itsPtr.get();}
X* get() {copy(); return itsPtr.get();}


so that depending on the context of the underlying objects' method (const or non const) you will get the correct behaviour - either shared or generate a copy.

so if used on an image object, cow_ptr< image> with the following methods ...

void set_pixel( int x, int y, rgba c); // should invoke copy behaviour to generate a new image and set the pixel on the copy
rgba get_pixel( int x, int y) const ; // will not generate a copy.

Share this post


Link to post
Share on other sites
@visitor - Yes that would work... I just figured it'd be easier to add a call to COW() to the beginning of each function that needed the functionality, rather than going through and hand testing uniqueness etc. Of course then you could just make a function "COW" in each class that needs it does the whole "if (!Data.unique)" thing, but why do that when it can just be given to you ready to go? Of course than I have to wrap everything in an else statement if it IS unique. Consider the more likely case where there is
void SetW(int W) {
COW();
Data->Width = W;
}

Here "H" retains the previous value. What about when we have a BUNCH of data members?

On a side note... I could of sworn I didn't put my "test case" in the code I put in here... and it turns out I was right. I didn't put my test case in here. You just so happened to use "10, 20", and then "5, 6" respectively, as did I. That's kinda freaky.

Cheers
-Scott

Share this post


Link to post
Share on other sites
@chairthrower - Yeah I thought about this. Unfortunately this means I would have to edit code in hundreds of places, or at least anywhere I declare an object of the "Image" type (in this case). I was looking for a way that I could just edit the Image class and not have to search through 2000+ files if I didn't need to. Do you think the cow-pointer way is beneficial enough to be worth doing that?

Also, and more importantly, if I have a function, say Image::CopyFrom and under most circumstances it makes a complete copy, but under some circumstances under the right conditions it would be better to just reference the "From", until written to, I'm back to putting the smart pointer inside the class and intruding. Then I'm back to making multiple calls per non-const function, and then I'm back to how do I make this easier? Which brings me to my original post. Hmmmm...

Cheers
-Scott

[Edited by - popsoftheyear on October 22, 2008 6:33:09 PM]

Share this post


Link to post
Share on other sites
Quote:

I just figured it'd be easier to add a call to COW() to the beginning of each function that needed the functionality, rather than going through and hand testing uniqueness etc. Of course then you could just make a function "COW" in each class that needs it does the whole "if (!Data.unique)" thing, but why do that when it can just be given to you ready to go? Of course than I have to wrap everything in an else statement if it IS unique. Consider the more likely case where there is


In the simplest case you could probably provide a free templated function to do the COW. I think the test for uniqueness is quite important though, as otherwise you'd reallocate each object every time a mutator is accessed, even though no data is shared.

Share this post


Link to post
Share on other sites
Quote:
I was looking for a way that I could just edit the Image class and not have to search through 2000+ files if I didn't need to. Do you think the cow-pointer way is beneficial enough to be worth doing that?


Theoretically it should only require a simple refactor, the changing or retyping of Image type and then use of operator -> as the accessor rather than dot notation for member method select.

Then again if its really spread across 2000 lines of code, then these sorts of changes (small but everywhere) can make a mess in source control when trying to diff revisions for changes if the global change occurs somewhere between revisions.

Additionally as a one off use-case (as anything to do with COW is likely to be) maybe its better to adapt the behaviour so that it occurs behind the interface (interface rather than generic good programming practice).

I am trying to remember why i didnt continue using it - it's possible that I was trying to work with code (legacy/3rd party api's)that didnt enforce const correctness. its a nice trick of template ahckery - to extract performance if the const stuff is correctly propagated through the code,

Quote:
Also, and more importantly, if I have a function, say Image::CopyFrom and under most circumstances it makes a complete copy, but under some circumstances under the right conditions it would be better to just reference the "From", until written to, I'm back to putting the smart pointer inside the class and intruding. Then I'm back to making multiple calls per non-const function, and then I'm back to how do I make this easier? Which brings me to my original post. Hmmmm...


I am not quite sure I understand this - copy or assignment operations should just transmit the underlying object around unaffected. for operations that change the data a test is done to determine if the object is unique in which case it isnt copied (I am slowly remembering how cow works - thanks Visitor).

pause, mnnn,

I think I see what you are getting at, if performance is overiding factor and operations such as resizing /cropping /selecting a subarea are all common, then this could potentially done just by specifying the additional data needed such as offsets and strides which would avoid copying the underlying pixel buffer. clearly a basic non-intrusive cow cannot do this.

Share this post


Link to post
Share on other sites
@visitor - Yeah, my version tests for uniqueness (see source in the first post). My only point was that to do the test explicitly for EVERY scenerio that might need it, as opposed to calling some COW() function that does it for you would be much better. By the time I get there, I'm half-way to writing my own.

@chairthrower - And yeah the basic non-intrusive version just won't cut it for me. I did consider using a smart pointer with copy-on-write semantics to control the "ImageData" though... but at that point it seemed just a little bit simpler to do it like this.

Anyway thanks for the input. I'm certain I feel more confident about it now.

Cheers
-Scott

Share this post


Link to post
Share on other sites

This topic is 3339 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this