I use C-style casts in my 15K LOC C++ project, 90% of the times for casts between child and base classes.
Even when I read that it is bad to use them, and that they can result in severe errors, as they are not type safe as the C++ casts, I still feel perfectly fine and comfortable with using them.
I have not experienced a single bug in my project so far that was caused by, for example, an accidentally mistyped C-Style cast - really.
There are two main reasons I have not been using them:
- I didn't know enough about them yet
- I didn't like their syntax, they are more verbose and harder to read for me
My questions:
- (Why) Should I refactor my 开发者_如何学运维project to use C++-style casts?
- Why should I use C++-style casts for my future projects?
I use as good as all other advantages C++ offers me, from OOP including virtual and abstract base classes, namespaces, the STL, and so on, just not the new type casting syntax. The argument "Why aren't you just using C then?" doesn't work for me.
The main advantage of the C++-style casts is, as you've mentioned, the type safety. Each cast in C++ handles one specific sort of conversion (or a family of related conversions) and so the compiler can go and check that you're not accidentally doing more conversions that you intended, or a sequence of conversions that fundamentally isn't safe.
One thing to think about is that while it's great that you feel comfortable using C-style casts, and while it's great that you haven't made any mistakes with them, other people working on the code base might not be as facile with these casts as you are. Using the casting operators makes the code more self-documenting. If you have a C-style cast somewhere, someone else reading the code might not be able to immediately infer what it is that you're doing. If they see something like
T* ptr = (T*) var;
They might not immediately be able to tell if this is
- A cast from a base class to a derived class or the other way around.
- A cast to strip
const
ness off ofvar
- A cast from an integral type to a pointer.
While they can probably glean this from context, it's a lot more obvious what's going on if you use a cast like
T* ptr = static_cast<T*>(var);
or
T* ptr = const_cast<T*>(var);
Another reason to prefer the C++-style casting operators is that they make the code more resilient to change. For example, suppose I have this function:
void DoSomething(Base* ptr) {
Derived* derived = (Derived *) ptr;
DoSomethingElse(derived);
}
Now, suppose that I realize that this function isn't supposed to make any changes to its argument, so I decide to mark it const
. For example:
void DoSomething(const Base* ptr) {
Derived* derived = (Derived *) ptr;
DoSomethingElse(derived);
}
But now we have a problem - my C-style cast which used to just do a downcast now also strips off const
ness. This can lead to an easy bug where DoSomethingElse
mutates the pointer I pass in, even though the DoSomething
method itself promises not to do this. If instead I wrote this code as
void DoSomething(Base* ptr) {
Derived* derived = static_cast<Derived *>(ptr);
DoSomethingElse(derived);
}
And then change the code by adding const
:
void DoSomething(const Base* ptr) {
Derived* derived = static_cast<Derived *>(ptr);
DoSomethingElse(derived);
}
Now I'll get a compiler error telling me that my old cast is broken, which can lead me to discover that there is a logical error in the code (namely, that DoSomethingElse
mutates its argument, so I can't naively make ptr
a pointer-to-const
.
So in short, using the C++ casting operators makes the code more readable and more maintainable. It makes the logic behind the code more explicit. And it makes the code less bug-prone by having the compiler catch errors either as you're making them or later on as you go back and change old code. I would recommend trying to use C++-style casting operators in the future for these main reasons.
As for whether you should go back and try to replace your current C-style casts with C++-style casts, that's really up to you. As an exercise, I'd suggest doing it just to practice learning what sorts of casts you're using. Plus, you might find a logic error in there, which would make the search worth your while!
In a company I used to work for, we once had to port a several million lines of code app to an new platform. What started out as "just another port to just another Unix platform" (the code already run on Windows, OSX, and half a dozen Unix-like platforms), turned out a huge problem. IIRC, the reason was that this platform had rather strict alignment requirements, and some of our casts were tripping hardware exceptions due to misalignment.
This would have been easy enough to fix, if it weren't for the fact that a certain percentage of these casts were C-style casts. It was impossible to effectively grep for these. Searches turned up literally tens of thousands of lines of code, which all had to be inspected manually by human beings, 99.99% of which were totally irrelevant.
It didn't help that us humans tended to miss a few of these hard-to spot casts, which would then have to be found in another round of reviewing tens of thousands of lines of code, 99.99% of them exactly the same as in the first round.
That task was nigh impossible to finish in the time given, and almost broke the company's back, because they were facing a severe contract penalty if we slipped the deadline.
Needless to say that afterwards it was decided to replace all C-style casts by their C++ counterpart. However, had this policy existed before we had to make that port...
(Why) Should I refactor my project to use C++-style casts?
Yes. For the same reason you should use them in the future.
Why should I use C++-style casts for my future projects?
The list is actually fairly long but I'll go over the most important.
First, they clearly state the intent of the cast. Reading "static_cast" you know that the author intended to perform a static cast, rather than a reinterpret or const.
Second, they do one, and only one thing. You can't accidentally cast away constness for example.
They're easy to search for. Search for "const_cast" to find all casts to/from const. How would you do this with C-style? You couldn't.
They don't change cast kind when local code semantics change. For example, (Widget*)(blah)
will silently change to a reinterpret cast if Widget stops inheriting from whatever type blah was a pointer to. It also changes to a reinterpret cast if the Widget definition is not locally available. If you use new-style casts instead you'll get compiler vomit rather than a silent reinterpretation of the statement.
You can do a dynamic cast. C-style casts can't do this.
Well two parts:
(Why) Should I refactor my project to use C++-style casts?
If your code works. Then don't fix it. Leave it as it is. Finding the C casts can be quite hard anyway. If you are working on the code and come across a C cast then change it as required.
Why should I use C++-style casts for my future projects?
Because C casts are easy to get wrong.
If 90% of your casts are from sub-class to base-class, then removing them would be a start - they shouldn't be needed. If you have lots of casts from base to sub, then you are doing something else wrong.
The rest, you're telling the compiler that something strange is going on and you're breaking the rules - either the rules of the type system with a reinterpret_cast or a const_cast, you're risking representation with a static_cast to a smaller integral type, or you're breaking LSP and having to know about specific type of a sub-class when given a base-class.
Making those cases explicit is a good thing.
(Why) Should I refactor my project to use C++-style casts?
Why should I use C++-style casts for my future projects?
There's some good answers (including my own IMO) to the second question on this question.
For the first question, if I were in your position, I would substitute the C++ style casts for the C-style casts whenever I was already editing a file. It's probably not worth setting aside a day or so to edit all your code and re-compile it, but it is probably worthwhile to incrementally improve your code-base when you're already working on it.
If you have time set aside for re-factoring activities, this would be a good candidate for that.
Were I you, I would continue using C-style casts. You have good reasons: you are not experiencing the alleged deficiencies of C-style casts, the C++ casts are more verbose, the C++ casts are harder to read and less familiar to you.
More generally, as a C++ programmer you will often read or be told that some way of doing things is the Old, Wrong, C way of doing things and that you should use the New, Correct, C++ way of doing things. Some examples:
- you should use C++ style casts instead of C style casts
- you should use references instead of pointers
- you should use the "const" keyword a lot
- you should use generics instead of void pointers or typecasts
- you should use initializer lists instead of initializing member variables in the body of constructors
- you should use constructors instead of a post-construction initialization function
- you should never use macros
You will be usually be told that the new way is safer, that it lets the compiler catch more errors, and that it makes the code clearer.
I suggest you view such advice with suspicion. Instead, consider: does using the new C++ feature solve a problem that you actually have? Does it create new problems (by the way, increased code verbosity and decreased readability are problems)? If the answer to the first question is no, or the answer to the first question is yes, you should consider sticking with the Old, Wrong, C way.
A side comment on code clarity: verbosity reduces clarity if the added text does not help a reader understand the program. Were I you, I would view the claim that the C++ style casts improve code clarity skeptically, since you, a reader of your program, do not perceive a benefit.
I used to be the kind of person that always tried to use the New, Correct, C++ way. I am no longer so dogmatic. The essay at the below link was a big factor in changing my mind.
http://yosefk.com/c++fqa/
精彩评论