[C++] Problems with templates and friends

Started by
4 comments, last by ApochPiQ 12 years, 8 months ago
I have an idea for a Tween class that looks great on paper, but I am running into trouble trying to implement it. I want to be able to create Tween objects that take in a variable, and adjust it in different ways across a given time span. I then want to be able to have objects instantiate Tweens which will run automated. Essentially I want to be able to:
[source cpp]
// give object a Tween* vector
vector<Tween*> tweenVec;

// somewhere within the Object's code
// to tween position from its current (1, 1) to (100, 100) over 1000ms
Tween<float>* tempTween = new Tween<float>( &m_position.X, 100, 1000 )
tweenVec.add( tempTween );
tempTween = new Tween<float>( &m_position.Y, 100, 1000 )
tweenVec.add( tempTween );

// then in the Object's update code:
if ( !tweenVec.empty() )
{
for each tween t
t->update( currentTime )
if t->isFinished
// delete the object and remove the pointer from the vector
}
[/source]

I thought, through my limited understanding of friends, that if I specified the Tween class as a friend in the Object's declaration code that it could access protected members. I tried making a Tween<T> a friend, but then it wanted me to also make Object a template, which is understandable but very undesirable.

How would someone go about implementing this so object could have a vector of Tween pointers that have access to its protected data? Would knowing which data types it will work on be helpful? As in, explicitly making it friends with Tween<int>, Tween<float> etc? Also, will all of this be undone when I make derived Tweens with specific agorithms?
Advertisement
Note that Tween<float> is a completely different type than Tween<int> so you can't have Tween* without the template type. You must always specify the type. What you can do is having a abstract base class that all your Tween<T> inherit from.
Something like this:
class TweenBase
{
public:
virtual void update(int time) = 0;
};

template <typename T>
class Tween : public TweenBase
{
public:
virtual void update(int time) { .. };
};

Then you can have an std::vector<TweenBase*> to store the pointers in.

I don't understand exactly why you want to use a friend. What protected data are you talking about?
The protected data that I was talking about would be members of an Object or Sprite class, like its (protected) position or scale variables. I was looking to give the Tween a reference to a member variable at the Tween's instantiation, and just let it run until it dies. Then on each render cycle, the object would be drawn using its member variables, which have been manipulated by the Tween in its Update() function.

The idea of using the abstract base class is something I will explore when I go back to it tonight, thanks for your help.
The friend keyword allows class A to access class B's private members by going through class B.

That is:

class A
{
private:
int myPrivInt;

// allow A to see my internals
friend class B;
};
class B
{
A myA;

void SomeFunc()
{
// I can reach myPrivInt through A, because it declared me as a friend
myA.myPrivInt = 5;
}
}



That's not what you're doing though. You're binding a pointer to m_position.Y. This allows anyone owning that pointer to modify m_position.Y without going through A. You don't have to do anything with friends. If you have a pointer to something, you can change it, even if it's a private member within a class.

Note that this breaks encapsulation and is typically seen as bad OO programming. You're bypassing the very reason for making m_position private in the first place. That doesn't mean it should always be avoided. We often write code like this because it's more efficient than alternatives that are more OO.

In this case, I think you're safe as long as you make sure that Object maintains strict ownership over the Tween objects. I recommend you enforce this by making tweenVec a boost::ptr_vector, but in the absence of boost I'd just make sure you're deleting each tweenVec object in the Object destructor.

I'm having trouble thinking of good ways of doing this without breaking encapsulation. You could do something with shared_ptrs and boost::functions to retrieve and set the current value, surely, but I don't think it would end up being very nice.
Great explanation of friend classes thank you. That is exactly what I thought they did, but I did not realize that you could get around variables being private through pointer use (as bad practice as it may be).


In this case, I think you're safe as long as you make sure that Object maintains strict ownership over the Tween objects. I recommend you enforce this by making tweenVec a boost::ptr_vector, but in the absence of boost I'd just make sure you're deleting each tweenVec object in the Object destructor.

Yes, the object itself will instantiate the Tween objects, add them into its vector, and delete them when they finish (and delete any remaining when the object's destructor is called). No other objects will have access to this vector or the individual Tweens.


I'm having trouble thinking of good ways of doing this without breaking encapsulation. You could do something with shared_ptrs and boost::functions to retrieve and set the current value, surely, but I don't think it would end up being very nice.

This was somewhat like an approach my partner and I discussed, where upon Tween instantiation you would pass it a function pointer that would later be used to pass the tween'd value back to the object every Update(). This approach would have worked, but I am trying hard to maintain the simple/clean interface for the Tween class, and felt that the function pointers made it feel sloppy and unnecessarily abstract.

Thanks again for the assistance.

Also, is this concept something that is done regularly but I just never ran across? It seems like a real handy class to have around, but I have never seen it before.
The typical solution is to implement tweening as a templated free function, not a class. Then you just invoke Tween(startFoo, endFoo, startT, endT, currentT, interpolationMethodEnum); and you're done.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

This topic is closed to new replies.

Advertisement