I would like some comments on somthing that has been bugging me quite a bit. Implementing parts of the API can lead to duplication of code inside the same file. In my example, let's take two functions, one operating on a ASCII string and one one a Unicode string, let's call them "funcA" and "funcW".
Upon implementation, the only real difference between these are the parameters, "funcA(const char *p)" and "funcW(const WCHAR *p)". What is the best way to keep the code maintainable and making sure that we don't have to fix a single bug in two places in the same file? I see 3 (maybe more) approaches:
1. Implement funcA, making funcW and exact copy with the relevant parameters and local variables changed;
2. Implement a generic solution as a macro and get both funcA and funcW to call it will specific parameters;
3. Do some preprosessor magic as implemented in dlls/msvcrt/scanf.[hc]
What is the general feeling as to the right way to approach this? What is the preferred approach? (And maybe it is something I haven't listed here.)
At this point, I've been following approach 1, which makes me a bit uncomfortable with regardsto code bloat and forever having to make sure that funcA is on par with funcW and vice versa. I've toyed with the idea of 2, but as Dimitrie rightly said, it is a bit C++-ish :) (Plus it doesn't do wonders for debugging...) Number 3, yes it works, but it can add extra files. (Altough it almost seems the best of the 3 I've listed.)
Your comments would be appreciated, somewhere there is the best solution that will be agreeable to all and which makes sure things stay relatively simple. (Well, simplicity across two functions at least.)
Greetings, Jaco
Le mar 05/11/2002 à 13:33, Jaco Greeff a écrit :
I would like some comments on somthing that has been bugging me quite a bit. Implementing parts of the API can lead to duplication of code inside the same file. In my example, let's take two functions, one operating on a ASCII string and one one a Unicode string, let's call them "funcA" and "funcW".
Upon implementation, the only real difference between these are the parameters, "funcA(const char *p)" and "funcW(const WCHAR *p)". What is the best way to keep the code maintainable and making sure that we don't have to fix a single bug in two places in the same file? I see 3 (maybe more) approaches:
- Implement funcA, making funcW and exact copy with the relevant parameters
and local variables changed;
- Implement a generic solution as a macro and get both funcA and funcW to
call it will specific parameters;
- Do some preprosessor magic as implemented in dlls/msvcrt/scanf.[hc]
What is the general feeling as to the right way to approach this? What is the preferred approach? (And maybe it is something I haven't listed here.)
From what I saw in a couple of places: from funcA, do some magic on the
parameters (convert to Unicode), then call funcW, then reconvert the result from Unicode to ASCII. Yes you have to convert twice, but you use the same function.
Hope it helps you.
At this point, I've been following approach 1, which makes me a bit uncomfortable with regardsto code bloat and forever having to make sure that funcA is on par with funcW and vice versa. I've toyed with the idea of 2, but as Dimitrie rightly said, it is a bit C++-ish :) (Plus it doesn't do wonders for debugging...) Number 3, yes it works, but it can add extra files. (Altough it almost seems the best of the 3 I've listed.)
Your comments would be appreciated, somewhere there is the best solution that will be agreeable to all and which makes sure things stay relatively simple. (Well, simplicity across two functions at least.)
Greetings, Jaco
Vincent
On November 5, 2002 01:33 pm, Jaco Greeff wrote:
I know there has been answers, but here are my $0.02:
- Implement funcA, making funcW and exact copy with the relevant
parameters and local variables changed;
- Implement a generic solution as a macro and get both funcA and funcW to
call it will specific parameters;
- Do some preprosessor magic as implemented in dlls/msvcrt/scanf.[hc]
These should be avoided as much as possible IMO. For too many reasons to list here. Option 4: 4. Implement the meat in funcW, and make funcA a wrapper that just converts the arguments from A->W and back (if need be). Is the most general. Sometimes there is Option 5: 5. Implement the meat in a funcT that takes a boolean as an argument telling it if the input is A or W, and just have the func[AW] forward the call to funcT This works only within one module, and there are other problems. That is, if it does any non-trivial work with the string, that might require that be converted between A<->W, it's better to go for 4, to avoid multiple conversions (which are expensive).
"Dimitrie O. Paun" dpaun@rogers.com writes:
- Implement the meat in a funcT that takes a boolean as an argument telling it if the input is A or W, and just have the func[AW] forward the call to funcT
This works only within one module, and there are other problems. That is, if it does any non-trivial work with the string, that might require that be converted between A<->W, it's better to go for 4, to avoid multiple conversions (which are expensive).
Of course another problem with option 5 is that you lose all type checking, so the compiler cannot warn you when you confuse the string types, plus it requires ugly casts. Better avoided IMO.
On November 5, 2002 08:23 pm, Alexandre Julliard wrote:
Of course another problem with option 5 is that you lose all type checking, so the compiler cannot warn you when you confuse the string types, plus it requires ugly casts. Better avoided IMO.
I have to agree, even if in listview.c I used this technique. I did manage to avoid the ugly casts, and the compiler warnings are not really a problem (for various reasons), but it required careful coding, and at the end of the day, I don't think I would want it generalized in other parts of the code, despite it working to our advantage in the listview.c case. The pitfalls are not worth the trouble in general.