Sign in to follow this  
Hodgman

[C++] Is this evil? Operator overloading for adapting APIs.

Recommended Posts

Im doing some maintenance work at the moment. I've got to take an old code-base, strip off an old API that it sends data to and instead, have it send data to a newly build API. The code that uses the old API is quite convoluted in places, but it has been tested to be very solid. I don't really want to edit any of this 'user code', instead I would just like to replace the api object with a different class and recompile. The problem is that a large part of the old API's public interface are variables, not functions. For example, the old API publicly exposed a 2D array of widgets, whereas my new API doesn't.
api.pWidget->ptrs[x][y]->SetVisible(z);
As you can see below, the new API is a lot simpler than the old one - it just has functions and hides it's internal details:
class NewApi
{
public:
	virtual bool SetVisible( int foo, int bar, bool state ) = 0;
};
struct OldApi
{
	class Widget
	{
	public:
		class Label
		{
		public:
			void SetVisible(bool){}
		};
		Label *ptrs[MAX_FOO][MAX_BAR];
	};
	Widget* pWidget;
};
Obviously I can't just recompile the 'user' code using the new API, because the interfaces are completely different... which is why I came up with the following adaptor class. This class looks exactly like the old API as far as the 'user' can tell (which means all of the existing code will continue to compile), but it actually uses the new API:
//WARNING - UGLY C++ CODES BE HERE!
struct ApiAdaptor
{
	struct Widget
	{
		Widget* operator->() { return this; }
		struct
		{
			struct FooIndex
			{
				FooIndex( int f ) : f(f) {} int f;
				struct FooBarIndex
				{
					FooBarIndex( int f, int b ) : f(f), b(b) {} int f,b;
					FooBarIndex* operator->() { return this; }

					void SetVisible(bool s){ g_pNewApi->SetVisible(f,b, s); }
				};
				FooBarIndex operator[]( int bar ) { return FooBarIndex(f,bar); }
			};
			FooIndex operator[]( int foo ) { return FooIndex(foo); }
		} ptrs;
	} pWidget;
};
Is this adaptor too evil? Should I give in and just edit the 'user' files to use the new API (and maintain two branches of that code), or should I put up with the ugliness of the adaptor to lessen the overall amount of maintenance work?

Share this post


Link to post
Share on other sites
To me this seems a lot more like a job for a regexp find-and-replace in the user code. Introducing that adapter might make your job easier, but I pity the person that has to do the next bit of maintenance (and consider that the next person may be you!).

Just try a regexp find-and-replace in the old API code - the convoluted syntax of the old calls make me think it would be highly unlikely to get any false hits.

Share this post


Link to post
Share on other sites
If the majority of your maintenance is converting variable access into function calls, then I agree that you should probably just use a regexp find-replace. Using MSVC's syntax:
Find:      OldApi\.pWidget-\>ptrs\[{.+?}\]\[{.+?}\]-\>SetVisible\({.+?}\);
Replace: NewApi\.SetVisible\(\1, \2, \3\);
(disclaimer: off the top of my head, untested!)

If you actually need to add additional logic to a lot of variable accesses for it to work, then an interface would be appropriate to eliminate tons of duplicate code. So really it depends on exactly what the difference between the new and old code will be.

Share this post


Link to post
Share on other sites
Thanks for the reg-ex advice.

I'm still torn as to which approach to take. The reg-ex isn't a simple win because the old/existing code still needs to be maintained as well.

In other words, if I do a find replace then I will end up with two copies of that code to maintain.

Share this post


Link to post
Share on other sites
I missed the bit at the end of your original post that said you also need to keep the old API code around :) In that case, I would try to come up with some interface that can handle both APIs. But instead of using nested classes and operator overloading voodoo so that old code syntax can stay the same, I would modify the code to use the interface API (with regexp, etc.), and simply call out to either the old API or the new API internally. That way you can still maintain one code path without having to resort to "evil" C++, while properly abstracting the code so that you could potentially use even newer APIs later on.

Share this post


Link to post
Share on other sites
Quote:
Original post by Hodgman
Thanks for the reg-ex advice.

I'm still torn as to which approach to take. The reg-ex isn't a simple win because the old/existing code still needs to be maintained as well.

In other words, if I do a find replace then I will end up with two copies of that code to maintain.


It's not 100% clear to me why the change causes a branch. Do you mean that the same API is used for other code besides the code you are refactoring, or for code you cannot touch (or cannot rely on being available at certain client sites)? Are there already multiple branches of this code?
Or are you afraid that you will affect the stability of the code with your refactoring?

[Edited by - MadKeithV on October 24, 2008 5:05:49 AM]

Share this post


Link to post
Share on other sites
I like the idea of porting the code and writing an adapter for the old API instead. Seems like the way to go. What you have so far is my second choice though.

Share this post


Link to post
Share on other sites

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