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

Started by
6 comments, last by iMalc 15 years, 6 months ago
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?
Advertisement
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.
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
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.
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.
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.
What about porting the code over to the new API and writing an adapter for the old API?
Philip LutasMy site of randomness
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]
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
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.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

This topic is closed to new replies.

Advertisement