C ++ 프로젝트에서 C 스타일 캐스트를 사용하는 경우 C ++ 캐스트로 리팩토링 할 가치가 있습니까?
저는 15K LOC C ++ 프로젝트에서 C 스타일 캐스트를 사용하는데, 90 %는 하위 클래스와 기본 클래스 간의 캐스트에 사용됩니다.
내가 그들을 사용하는 것이 나쁘다는, 그들이 C ++ 캐스트 안전 입력하지 않는 한 그들은, 심각한 오류가 발생할 수 있음을 읽을 때도, 나는 여전히 기분이 완벽하게 정상적으로 그들을 사용하여 편안하게.
예를 들어 실수로 잘못 입력 된 C-Style 캐스트로 인해 발생한 버그는 지금까지 한 번도 경험하지 못했습니다.
내가 사용하지 않은 두 가지 주요 이유가 있습니다.
- 나는 그들에 대해 아직 충분히 몰랐습니다
- 나는 그들의 구문이 마음에 들지 않았고, 더 장황하고 읽기 어렵습니다.
내 질문 :
- (이유) C ++ 스타일 캐스트를 사용하도록 프로젝트를 리팩터링해야합니까?
- 향후 프로젝트에 C ++ 스타일 캐스트를 사용해야하는 이유는 무엇입니까?
저는 가상 및 추상 기본 클래스, 네임 스페이스, STL 등을 포함하는 OOP에서 C ++가 제공하는 다른 모든 이점만큼이나 새로운 형식 캐스팅 구문이 아닌 좋은 방법을 사용합니다. " 왜 그냥 C를 사용 하지 않습니까? "라는 주장은 저에게 효과가 없습니다.
C ++ 스타일 캐스트의 가장 큰 장점은 앞서 언급했듯이 형식 안전성입니다. C ++의 각 캐스트는 하나의 특정 유형의 변환 (또는 관련 변환 집합)을 처리하므로 컴파일러는 의도 한 것보다 더 많은 변환을 실수로 수행하지 않았는지 또는 근본적으로 안전하지 않은 일련의 변환을 수행하지 않았는지 확인할 수 있습니다. .
한 가지 생각해야 할 점은 C 스타일 캐스트를 사용하는 것이 편하다고 느끼는 것이 좋지만, 실수를하지 않았다는 것이 대단하지만 코드베이스에서 작업하는 다른 사람들은이 캐스트에 익숙하지 않을 수도 있다는 것입니다. 당신 그대로. 캐스팅 연산자를 사용하면 코드를보다 자체적으로 문서화 할 수 있습니다. 어딘가에 C 스타일 캐스트가있는 경우 코드를 읽는 다른 사람이 사용자가하는 일을 즉시 추론하지 못할 수 있습니다. 그들이 다음과 같은 것을 본다면
T* ptr = (T*) var;
그들은 이것이 사실인지 즉시 알 수 없을 수도 있습니다.
- 기본 클래스에서 파생 클래스로 또는 그 반대의 캐스트.
const
ness 를 제거하는 캐스트var
- 정수 유형에서 포인터로의 캐스트.
그들은 아마도 문맥에서 이것을 얻을 수 있지만, 다음과 같은 캐스트를 사용하면 무슨 일이 일어나고 있는지 훨씬 더 분명합니다.
T* ptr = static_cast<T*>(var);
또는
T* ptr = const_cast<T*>(var);
C ++ 스타일 캐스팅 연산자를 선호하는 또 다른 이유는 코드를 변경에 대해 더 탄력적으로 만들기 때문입니다. 예를 들어 다음과 같은 기능이 있다고 가정합니다.
void DoSomething(Base* ptr) {
Derived* derived = (Derived *) ptr;
DoSomethingElse(derived);
}
이제이 함수가 인수를 변경해서는 안된다는 것을 알고 있으므로 표시하기로 결정했습니다 const
. 예를 들면 :
void DoSomething(const Base* ptr) {
Derived* derived = (Derived *) ptr;
DoSomethingElse(derived);
}
하지만 이제 문제가 생겼습니다. 다운 캐스트를하던 제 C 스타일 캐스트도 이제는 const
ness를 벗겨냅니다 . 메서드 자체가이를 수행하지 않을 것을 약속 DoSomethingElse
하더라도 전달하는 포인터를 변경 하는 쉬운 버그가 발생할 수 있습니다 DoSomething
. 대신이 코드를 다음과 같이 작성했다면
void DoSomething(Base* ptr) {
Derived* derived = static_cast<Derived *>(ptr);
DoSomethingElse(derived);
}
그리고 다음을 추가하여 코드를 변경합니다 const
.
void DoSomething(const Base* ptr) {
Derived* derived = static_cast<Derived *>(ptr);
DoSomethingElse(derived);
}
이제 이전 캐스트가 깨 졌다는 컴파일러 오류가 발생하여 코드에 논리적 오류가 있음을 발견 할 수 있습니다 (즉, DoSomethingElse
인수 를 변경하므로 순진하게 ptr
포인터를 만들 수 없습니다. ~ const
.
즉, C ++ 캐스팅 연산자를 사용하면 코드를 더 읽기 쉽고 유지 관리하기 쉽습니다. 이는 코드의 논리를보다 명확하게 만듭니다. 또한 컴파일러가 오류를 만들 때 또는 나중에 돌아가서 이전 코드를 변경할 때 오류를 포착하도록함으로써 버그 발생 가능성이 줄어 듭니다. 이러한 주요 이유로 향후 C ++ 스타일 캐스팅 연산자를 사용하는 것이 좋습니다.
돌아가서 현재의 C 스타일 캐스트를 C ++ 스타일 캐스트로 교체해야하는지 여부는 실제로 귀하에게 달려 있습니다. 연습으로 어떤 종류의 캐스트를 사용하는지 배우는 연습을하는 것이 좋습니다. 또한 거기에서 논리 오류를 찾을 수 있으므로 검색 할 가치가 있습니다!
제가 일하던 회사에서 우리는 수백만 줄의 코드 앱을 새로운 플랫폼으로 이식해야했습니다. "다른 유닉스 플랫폼에 대한 또 다른 포트"(코드는 이미 Windows, OSX 및 6 개의 유닉스 계열 플랫폼에서 실행 됨)로 시작된 것이 큰 문제가되었습니다. IIRC, 그 이유는이 플랫폼이 다소 엄격한 정렬 요구 사항을 가지고 있었고 일부 캐스트는 정렬 불량으로 인해 하드웨어 예외가 발생 했기 때문입니다.
이 캐스트의 특정 비율이 C 스타일 캐스트 라는 사실이 아니었다면 고칠 수있을만큼 쉬웠을 것 입니다. 이것들 을 효과적으로 그렙 하는 것은 불가능했습니다 . 검색 결과 문자 그대로 수만 줄의 코드 가 나타 났으며,이 코드 는 모두 인간이 수동으로 검사해야했으며 , 그 중 99.99 %는 전혀 관련이 없었습니다.
우리 인간이 발견하기 어려운 캐스트 몇 개 를 놓치는 경향이 있다는 것은 도움이되지 않았습니다. 그런 다음 수만 줄의 코드 를 검토하는 또 다른 라운드 에서 발견되어야 합니다. 그 중 99.99 %는 첫 라운드.
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.
'Programing' 카테고리의 다른 글
iPhone에서 활동 표시기보기를 사용하는 방법은 무엇입니까? (0) | 2020.11.17 |
---|---|
Javascript 객체의 속성 이름을 반복하는 방법은 무엇입니까? (0) | 2020.11.17 |
TabControl을 ViewModel 컬렉션에 어떻게 바인딩합니까? (0) | 2020.11.17 |
Android에서 채워지지 않은 그림을 그리는 방법은 무엇입니까? (0) | 2020.11.17 |
업로드 된 파일을 디렉토리에 저장하기 전에 이름을 바꾸는 방법은 무엇입니까? (0) | 2020.11.17 |