I'm not sure of the vocabulary here, but hopefully I can make myself understood.
As I'm working through the winapi with a less-than-rock-solid knowledge of C++, I find a lot of typedef stuff that, for me, seems to overcomplicate the issue and add one more thing I have to remember.
For example, UINT
instead of unsigned int
, HBITMAP
which turns out is just a HANDLE
, and lots of others.
My question is, can / should I substitute the more generic version of the type when possible, and just cast it down when it's needed (and, what's this called)?
For example, I'd like to write
void SomeFunction(unsigned int some_int) { ... }
instead ofvoid SomeFunction(UINT some_int) { ... }
HANDLE hBMP = LoadImage(...); ImageList_Add(... (HBITMAP) hBMP ...);
instead ofHBITMAP hBMP = ...
Is this good for newcomers, bad practice in general, or what?
1) The Win32 API is actually C, not C++.
The distinction becomes more important if you consider stuff like MFC or ATL, both of which are "object-oriented", C++-only APIs. And both of which are mercifully becoming (have become?) obsolete.
2) Microsoft likes to use lots of macros and typedefs. I can't say whether that's "good" or "bad". It's simply a fact of life. And it'll become second nature to you if you work with Windows for any length of time.
3) Most importantly - Yes: you should definitely follow the Microsoft conventions when you use the Microsoft APIs. Using "HANDLE" when the MSDN page says "HANDLE" is a Good Thing. Similar advice holds for "LPxxx", "TRUE", "FALSE", "INVALID_HANDLE", "HRESULT" etc etc. If that's what it says in MSDN, then that's what you should use.
Not only will it make your code more readable ... but, surprisingly often, it can also prevent subtle bugs you might cause by "second guessing" the "true type".
Do not try to "second guess" the types. It's just a Bad Idea.
Following the standard conventions will make life easier, safer, more reliable and more portable.
IMHO...
Please don't
typedef
the obvious (i.e.UINT
forunsigned int
). Rather,typedef
to convey meaning.UINT
no better thanunsigned int
(some may argue that it's shorter, but seriously, it's not that much shorter).A
typedef
for a long name (like atypedef
forstd::map<unsigned int, flost>::const_iterator
) is, in my opinion, okay, because it improves readability.UINT
... not so much.A
typedef
to convey specific meanings for specific types/uses (likeHANDLE
) are decent.HANDLE
is, in my opinion, better than using rawvoid*
because a) I don't care ifHANDLE
is avoid*
, b) it conveys its purpose.A
typedef
for portability is nice (like a typedef for a 32-bit signed integer), as it allows for easier transitions between platforms/compilers.It is a bad practice. You should not use the actual types.
The typedef's are usually for portability on different platforms & compiler implementations. So you should refrain yourself from using actual types instead of the typedef'ed name. Using actual types will make your code more tightly coupled to the platform & compiler implementation you use, in fact it may not work correctly if this combination changes.
Also, the typedefed names are more intuitive and widely known to programmers than the actual types, which is an additional reason why you should stick to those.
Typedefs
are not only useful for portability, but also for future proofing. If MicroSoft decides at some point thatUINT
should be atypedef
for anunsigned long int
instead of anunsigned int
then usingUINT
will make sure your code continues to work. This type of change is highly unlikely, especially with code from large companies like MS, but the idea still holds:typedefs
are good for many kinds of abstraction. They are by no means bad, but you can occasionally find examples of poor usage. If you are using someone else's API, always use theirtypedefs
. They probably have a good reason for the abstraction.There are several reasons for using a typedef.
typedef unsigned int UINT
typedef int INT64
typedef HANDLE HBITMAP
I'd rather read
HBITMAP hBitmap = ...
thanHANDLE hBitmap = ...
for the same reason that I would (in general) not writeAnimal dog = new Dog()
because being more specific can help the reader of your code and you're not losing anything here because according to the Liskov substitution principle you can use thedog
instance everywhere you can use anAnimal
instance.You might also consider using
BOOST_STRONG_TYPEDEF
if you are using C++ with Boost, this basically creates a simple class, thus, giving you a real new type (which means that you cannot mix the uses like you can withHANDLE
andHBITMAP
.What you describe as casting is not really casting, because the types are identical, a
typedef
only creates an alias, not a new type. So, writingHANDLE hOtherBitmap = /* some code */;
HBITMAP hBitmap = (HBITMAP) hOtherBitmap;
is like writing
int i = /* some value */;
int k = (int) i;