기술 노트/Google C++ Style Guide

헤더 파일부터 코드 스타일까지: Google C++ Style Guide 적용기 (2021)

anothel 2021. 7. 6. 19:00

Google C++ Sytle Guide를 해석해가며 공부한 후 배운 점과 느낀 점에 대해서 서술하려 한다.

 

Background, Goals of the Style Guide


 

Background


C++는 수많은 Google의 오픈소스 프로젝트에 사용되는 주요 개발언어이다. 모든 C++ 개발자가 알고 있는 것처럼 C++는 강력한 기능들이 많지만, C++의 이러한 강력한 기능들은 버그를 일으키기 쉽고, 코드를 읽거나 유지 보수하기 어렵게끔 만드는 복잡성을 갖고 있다.

이 가이드의 목표는 C++를 사용한 개발을 하며 해야 할 것과 하지 말아야 할 것에 대해 아주 자세하게 서술함으로써 복잡성을 관리하기 위함이다. 이러한 규칙은 코드 베이스를 관리 가능한 상태로 유지하면서, C++ 개발자가 생산적으로 개발할 수 있게 한다.

우리는 Style(혹은 읽기 쉬움)을 'C++ 코드를 통제하는 규약'이라고 부른다. Style이라는 말은 사실 좀 부적절하다. 왜냐하면 이러한 규약들은 단지 파일 포맷팅만 하는 것을 넘어서기 때문이다.

구글이 진행하는 대부분의 오픈소스 프로젝트는 이 가이드의 요구사항을 따른다.

이 가이드는 C++ 튜토리얼이 아니며, 우리는 독자가 C++에 이미 익숙해져 있다고 가정한다.

더보기

C++ is one of the main development languages used by many of Google's open-source projects. As every C++ programmer knows, the language has many powerful features, but this power brings with it complexity, which in turn can make code more bug-prone and harder to read and maintain.

The goal of this guide is to manage this complexity by describing in detail the dos and don'ts of writing C++ code. These rules exist to keep the codebase manageable while still allowing coders to use C++ language features productively.

Style, also known as readability, is what we call the conventions that govern our C++ code. The term Style is a bit of a misnomer, since these conventions cover far more than just source file formatting.

Most open-source projects developed by Google conform to the requirements in this guide.

Note that this guide is not a C++ tutorial: we assume that the reader is familiar with the language.

 

Goals of the Style Guide


우리는 왜 이 문서를 만들어야 하는가?

이 가이드가 제공해야 한다고 생각하는 몇 가지 핵심 목표에 대해서 서술하겠다. 해당 목표들은 모든 각각의 규칙에 대한 기저를 이루는 근본적인 이유가 있다. 이러한 이유들을 표면화함으로써, 논의를 바탕으로 하여 우리의 폭넓은 커뮤니티에게 왜 이러한 규칙이 시행되는지, 왜 각각의 결정을 내리게 되었는지에 대한 이유들을 명확히 하기를 바란다. 만약, 각각의 규칙이 어떤 목표로 시행되고 있는지 이해한다면, 규칙이 삭제될 때(몇몇은 그렇게 될 수도 있다.) 혹은 가이드에서 규칙을 변경하기 위한 약간의 분쟁 혹은 대안이 필요할 때 모두에게 명확해질 것이다.

우리가 현재 보고 있는 스타일 가이드의 목표:

더보기

Why do we have this document?

There are a few core goals that we believe this guide should serve. These are the fundamental whys that underlie all of the individual rules. By bringing these ideas to the fore, we hope to ground discussions and make it clearer to our broader community why the rules are in place and why particular decisions have been made. If you understand what goals each rule is serving, it should be clearer to everyone when a rule may be waived (some can be), and what sort of argument or alternative would be necessary to change a rule in the guide.

The goals of the style guide as we currently see them are as follows:

스타일 규칙은 그 임무를 다해야 한다

  • 우리의 모든 엔지니어들이 규칙을 기억하는 것을 요청하기 위함을 정당화할 수 있을 만큼 스타일 규칙의 유익성이 커야 한다. 유익성은 스타일 규칙 없이 우리가 얻을 수 있는 코드 베이스와 비교하여 측정된다. 그래서 매우 해로운 관행에 기댄 규칙을 만약 사람들이 어찌 됐든 사용하지 않는다면, 여전히 작은 유익성을 갖고 있다. 주로 이러한 개념은 우리가 사용하고 있다기보다는 갖고 있지 않다는 것에 대해서 설명하고 있다: 예를 들어, goto구문은 앞으로 나올 여러 개념들을 위반하고 있다. 하지만 이미 거의 사라졌기 때문에 이 가이드에서는 다루지 않는다.
더보기

Style rules should pull their weight

  • The benefit of a style rule must be large enough to justify asking all of our engineers to remember it. The benefit is measured relative to the codebase we would get without the rule, so a rule against a very harmful practice may still have a small benefit if people are unlikely to do it anyway. This principle mostly explains the rules we don’t have, rather than the rules we do: for example, goto contravenes many of the following principles, but is already vanishingly rare, so the Style Guide doesn’t discuss it.

코드를 작성하는 사람 말고 읽는 사람을 위해 최적화해라

  • 우리의 코드 베이스(그리고 대부분의 이미 제출된 컴포넌트)는 상당히 오랜 기간 동안 계속될 것으로 예상된다. 결과적으로, 코드를 작성하는 것보다 읽는 데에 더 많은 시간을 쓸 것이다. 우리는 코드를 작성할 때 쉬운 것보다는, 우리의 보통의 엔지니어가 코드를 읽기, 유지보수 및 디버깅을 위한 최적화를 하기로 결정했다. "읽는 사람을 위한 트레이스 로그를 남겨라"는 특히나 흔한 말이다: 코드에서 일반적이지 않거나, 무언가 갑작스러운 정보(예를 들어, 포인터의 주소 값을 넘길 때)가 나올 때, 코드를 읽는 사람을 위해 포인터의 사용을 순간에 원문의 힌트를 남겨놓는 것은 아주 값지다.(std::unique_ptr은 호출되는 장소에서 소유권 이전을 명료하게 입증한다.)
더보기

Optimize for the reader, not the writer

  • Our codebase (and most individual components submitted to it) is expected to continue for quite some time. As a result, more time will be spent reading most of our code than writing it. We explicitly choose to optimize for the experience of our average software engineer reading, maintaining, and debugging code in our codebase rather than ease when writing said code. "Leave a trace for the reader" is a particularly common sub-point of this principle: When something surprising or unusual is happening in a snippet of code (for example, transfer of pointer ownership), leaving textual hints for the reader at the point of use is valuable (std::unique_ptr demonstrates the ownership transfer unambiguously at the call site).

기존 코드에서 일관성을 유지하라(변하지 마라)

  • 우리의 코드 베이스를 통해 하나의 스타일을 일관되게끔 사용하는 것은 우리가 다른 (더 중요한) 이슈에 집중하게 한다. 또한, 일관성은 자동화를 할 수 있게 한다: 코드 혹은 #include의 포맷을 만들기 위한 툴은 코드가 tooling의 기대에 부합할 때 정삭 작동한다. 여러 상황에서, "일관성을 유지하라" 규칙은 "하나만 골라서 그것에 대해서 걱정하지 마라"에 기인한다; 이 관점에서 유연성을 허락하는 것에 대한 잠재적인 값어치는 사람들이 이 점에 대해서 논쟁을 벌이게 하는 것보다 더 많은 비용을 발생하게 한다.(다른 사람들이 논쟁을 벌이는 것이 유연성을 허락하는 것보다 비용이 덜 드니, 유연성을 허락하는 것이 부정적이다.) 하지만, 일관성에 대한 한계는 존재하고; 명확한 기술적 논쟁이나 장기적인 지시가 없을 때 좋은 최종결정자 역할을 한다. 이러한 상황은 아주 지역적인 것에 적용된다(각 파일, 또는 밀접하게 연관된 인터페이스). 일반적으로 일관성은 새로운 스타일에 대한 유익성 혹은 시간이 지나면서 더 새로워지는 것에 수렴하는 코드 베이스의 경향에 대한 고민 없이, 오래된 스타일의 코드에서 사용하기 위한 타당한 이유로써 사용되어서는 안 된다.
더보기

Be consistent with existing code

  • Using one style consistently through our codebase lets us focus on other (more important) issues. Consistency also allows for automation: tools that format your code or adjust your #includes only work properly when your code is consistent with the expectations of the tooling. In many cases, rules that are attributed to "Be Consistent" boil down to "Just pick one and stop worrying about it"; the potential value of allowing flexibility on these points is outweighed by the cost of having people argue over them. However, there are limits to consistency; it is a good tie breaker when there is no clear technical argument, nor a long-term direction. It applies more heavily locally (per file, or for a tightly-related set of interfaces). Consistency should not generally be used as a justification to do things in an old style without considering the benefits of the new style, or the tendency of the codebase to converge on newer styles over time.

적절한 때에 폭넓은 C++ 커뮤니티와 함께 일관성을 유지하라

  • C++를 사용하는 다른 조직과 함께하는 일관성을 유지하는 것은 우리의 코드 베이스 내의 일관성을 유지하는 것과 같은 이유로 가치가 있다. 만약, C++ standard의 기능이 문제를 해결한다면 혹은 몇몇 관용어(통상적으로 사용하는 코드)가 광범위하게 알려지고 받아들여졌다면 그것을 사용하기 위한 논쟁이 있을 것이다. 하지만, 때때로 C++ standard의 기능과 관용어들에는 결함이 있거나, 우리의 코드 베이스의 필요를 염두에 두지 않고 설계되기도 한다. 이러한 경우 (아래에 설명된 대로) C++ standard를 사용하지 못하게 하거나, 금지하는 것이 적절하다. 경우에 따라서 우리는 탁월성이 인지되지 않았거나, 코드 베이스를 standard 인터페이스로 전환하기에 값이 불충분한 라이브러리보다 직접 개발한 혹은 C++ Standard 위에서 정의된 라이브러리로 만들어진 써드파티 라이브러리를 더 선호할 수 있다.
더보기

Be consistent with the broader C++ community when appropriate

  • Consistency with the way other organizations use C++ has value for the same reasons as consistency within our codebase. If a feature in the C++ standard solves a problem, or if some idiom is widely known and accepted, that's an argument for using it. However, sometimes standard features and idioms are flawed, or were just designed without our codebase's needs in mind. In those cases (as described below) it's appropriate to constrain or ban standard features. In some cases we prefer a homegrown or third-party library over a library defined in the C++ Standard, either out of perceived superiority or insufficient value to transition the codebase to the standard interface.

놀랍거나 위험한 구조를 피하라

  • C++는 한눈에 바라볼 수 있는 것보다 더 놀랍거나 위험한 기능을 가지고 있다. 이러한 위험에 빠지지 않도록 몇 가지 스타일 가이드 제한들이 있다. 몇몇 규칙을 포기하는 것은 종종 프로그램의 정확함을 저해하는 위험을 야기하기 때문 제한에 대한 스타일 가이드의 포기에 대한 높은 기준이 있다.
더보기

Avoid surprising or dangerous constructs

  • C++ has features that are more surprising or dangerous than one might think at a glance. Some style guide restrictions are in place to prevent falling into these pitfalls. There is a high bar for style guide waivers on such restrictions, because waiving such rules often directly risks compromising program correctness.

우리의 일반적인 C++ 개발자가 유지 보수하기에 어렵거나 곤란한 상황에 놓일 구조를 피해라

  • C++는 코드에 포함된 복잡성 때문에 일반적으로 적절하지 않은 기능을 가지고 있다. 그것은 널리 사용되는 코드에서는 까다로운 언어구조를 사용하는 것이 더 적합할 수 있다. 왜냐하면 사용에 따라서 더 복잡한 실행의 유익성들이 급증할 것이고, 코드 베이스의 새로운 부분들과 함께 일할 때 복잡성에 대한 이해를 위한 비용은 발생할 필요가 없기 때문이다. 불확실할 때, 프로젝트 리더에게 요청함으로써 이러한 유형의 규칙들을 포기할 수 있다. 이것은 우리의 코드 베이스를 위해서 특히 중요하다. 왜냐하면 코드의 주인과 팀의 팀원들은 시간이 지남에 따라 변한다: 현재 코드의 일부와 함께 일하고 있는 모든 구성원이 그것을 이해하고 있다 할지라도, 몇몇 이해들은 지금으로부터 몇 년 후까지 유지되는 것에 대해서 보장되지 않는다.
더보기

Avoid constructs that our average C++ programmer would find tricky or hard to maintain

  • C++ has features that may not be generally appropriate because of the complexity they introduce to the code. In widely used code, it may be more acceptable to use trickier language constructs, because any benefits of more complex implementation are multiplied widely by usage, and the cost in understanding the complexity does not need to be paid again when working with new portions of the codebase. When in doubt, waivers to rules of this type can be sought by asking your project leads. This is specifically important for our codebase because code ownership and team membership changes over time: even if everyone that works with some piece of code currently understands it, such understanding is not guaranteed to hold a few years from now.

우리의 규모에 대해서 유의해라

  • 1억 라인 이상의 코드 베이스 그리고 수천 명의 엔지니어들과 함께하며, 몇몇 실수들과 한 명의 엔지니어를 위한 간소화는 많은 비용 지불을 초래할 수 있다. 예를 들어, 전역 네임스페이스 오염시키기를 피하는 것은 특히 중요하다: 수억 개 라인들의 코드 베이스를 가로지르는 이름 충돌은 만약 모든 사람들이 해당 전역 네임스페이스에 데이터를 넣었을 경우, 처리하기 어렵고 피하기도 어렵다. 
더보기

Be mindful of our scale

  • With a codebase of 100+ million lines and thousands of engineers, some mistakes and simplifications for one engineer can become costly for many. For instance it's particularly important to avoid polluting the global namespace: name collisions across a codebase of hundreds of millions of lines are difficult to work with and hard to avoid if everyone puts things into the global namespace.

필요에 의해서 최적화해야 함을 인정해라

  • 성능 최적화는 때때로 필요하고 적절할 수도 있다. 심지어 그들은 이 문서의 다른 개념과 충돌을 일으킬 수도 있다.
더보기

Concede to optimization when necessary

  • Performance optimizations can sometimes be necessary and appropriate, even when they conflict with the other principles of this document.

이 문서의 의도는 합리적인 제한을 동반한 최대한의 안내를 제공하기 위함이다. 늘 그렇듯, 보통의 감각과 좋은 맛은 만연해야 한다. 이에 따라 우리는 개인적인 선호나 팀의 규약뿐만 아니라 전체적인 Google C++ 커뮤니티에서 이미 세워진 규약에 대해서 참조한다. 기발하거나 흔치 않은 구조를 사용함에 있어서 의심을 갖고 주저해라: 금지 규정에 대한 부재는 통과하기 위한 라이선스와 같지 않다. 당신의 판단을 사용하고, 확신이 든다면, 프로젝트 리더에게 새로운 규정을 넣는 것에 대해서 주저 없이 요청해라.

더보기

The intent of this document is to provide maximal guidance with reasonable restriction. As always, common sense and good taste should prevail. By this we specifically refer to the established conventions of the entire Google C++ community, not just your personal preferences or those of your team. Be skeptical about and reluctant to use clever or unusual constructs: the absence of a prohibition is not the same as a license to proceed. Use your judgment, and if you are unsure, please don't hesitate to ask your project leads to get additional input.

C++ Version, Header Files


 

C++ Version


현재, 코드는 C++17 환경이어야 한다(예를 들어, C++ 2x는 안된다.). 이 가이드가 목표로 하는 C++ 버전은 시간이 지남에 따라 (공격적으로) 발전한다.

non-standard extensions을 사용하지 마라.

C++14, C++17에서 나온 기능을 사용하기 전에 다른 환경으로의 이식 가능성을 고려해라.

더보기
더보기

Currently, code should target C++17, i.e., should not use C++2x features. The C++ version targeted by this guide will advance (aggressively) over time.

Do not use non-standard extensions.

Consider portability to other environments before using features from C++14 and C++17 in your project.

 

Header Files


일반적으로, 모든 .cc 파일은 관련된 .h 파일이 있어야 한다. 몇몇 일반적인 예외사항이 있긴 한데, 유닛 테스트 혹은 main() 함수를 포함하고 있는 작은 규모의 .cc 파일이 그렇다.

헤더 파일의 올바른 사용은 코드에서의 성능, 가독성, 크기 등에서 큰 차이를 만들 수 있다.

지금부터 소개하는 규칙은 헤더 파일을 사용함에 있어서 다양한 함정을 통과할 수 있도록 안내한다.

더보기
더보기

In general, every .cc file should have an associated .h file. There are some common exceptions, such as unit tests and small .cc files containing just a main() function.

Correct use of header files can make a huge difference to the readability, size and performance of your code.

The following rules will guide you through the various pitfalls of using header files.

Self-contained Headers


헤더 파일들은 독립적(스스로 컴파일)이어야 하며, .h로 끝나야 한다. 포함을 위한 비 헤더 파일은 .inc로 끝나야 하며 아껴서 사용해야 한다.

모든 헤더 파일들은 독립적이어야 한다. 사용자들과 리팩터링 툴들은 헤더를 포함하기 위한 특별한 상태에 대해서 들러붙을 필요가 없다. 특히, 헤더는 헤더 가드를 갖고 있어야 하며, 필요한 모든 다른 헤더를 포함하고 있어야 한다.

템플릿과 인라인 함수들을 한 파일에 그들의 정의들로써 같은 파일에 놓는 것을 선호해라. 이러한 구성의 정의들은 반드시 그것들을 사용하는 모든 .cc 파일에 포함되어야만 하고, 그렇지 않으면 프로그램은 몇몇 빌드 환경설정에서 링크에 실패할 것이다. 만약 선언과 정의가 다른 파일에 있다면, 선언이 정의를 타동적으로 포함(include) 해야 한다. 이 정의들을 별도로 포함(include)된 헤더 파일들(-inl.h)로 옮기지 마라; 이러한 행위는 과거에는 일반적이었지만, 더 이상 허락되지 않는다.

예외적으로써, 템플릿 인수에 모든 관련된 집합을 명시적으로 인스턴스 화한 것 또는 클래스의 private 구현일 경우에는  템플릿을 인스턴스 화한 하나의 .cc 파일에만 정의되는 것을 허락할 수 있다.

독립적이지 않은 것을 포함하도록 디자인된 파일의 경우는 거의 없다. 이런 파일은 전형적으로 또 다른 파일의 중간에서와 같이 일반적이지 않은 위치를 포함(include)하도록 의도되었다. 아마도 헤더 가드를 사용하지 않았을 것이며, 전제조건(전처리기?)을 포함(include) 하지 않았을 것이다. 확장명이 .inc와 같은 파일의 이름을 지정해라. 아껴서 사용하고, 가능하다면 독립적인 헤더 파일을 선호하라.

더보기
더보기

Header files should be self-contained (compile on their own) and end in .h. Non-header files that are meant for inclusion should end in .inc and be used sparingly.

All header files should be self-contained. Users and refactoring tools should not have to adhere to special conditions to include the header. Specifically, a header should have header guards and include all other headers it needs.

Prefer placing the definitions for template and inline functions in the same file as their declarations. The definitions of these constructs must be included into every .cc file that uses them, or the program may fail to link in some build configurations. If declarations and definitions are in different files, including the former should transitively include the latter. Do not move these definitions to separately included header files (-inl.h); this practice was common in the past, but is no longer allowed.

As an exception, a template that is explicitly instantiated for all relevant sets of template arguments, or that is a private implementation detail of a class, is allowed to be defined in the one and only .cc file that instantiates the template.

There are rare cases where a file designed to be included is not self-contained. These are typically intended to be included at unusual locations, such as the middle of another file. They might not use header guards, and might not include their prerequisites. Name such files with the .inc extension. Use sparingly, and prefer self-contained headers when possible.

The #define Guard


모든 헤더 파일은 다양한 충돌을 방지하지 위해서 #define 가드를 사용해야 한다. 기호 이름은 다음과 같은 형식이어야 한다. <PROJECT>_<PATH>_<FILE>_H_.

유일성을 보장하기 위해서, 헤더 가드는 프로젝트 소스 구조의 전체 경로에 기반해야 한다. 예를 들어, foo/src/bar/baz.h 파일이 프로젝트의 foo 안에 있다면 다음과 같은 헤더 가드를 사용해야 한다.

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

...

#endif  // FOO_BAR_BAZ_H_
더보기
더보기

All header files should have #define guards to prevent multiple inclusion. The format of the symbol name should be <PROJECT>_<PATH>_<FILE>_H_.

To guarantee uniqueness, they should be based on the full path in a project's source tree. For example, the file foo/src/bar/baz.h in project foo should have the following guard:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

...

#endif  // FOO_BAR_BAZ_H_

Include What You Use


소스 파일 혹은 어딘가 다른 곳에 정의된 기호를 참조하는 헤더 파일의 경우, 그 기호의 선언이나 정의를 제공하기 위한 적절한 의도의 헤더 파일을 직접적으로 포함(include) 해야 한다. 다른 이유로 헤더 파일을 포함(include) 하지 말아야 한다.

타동적인 포함에 의존해서는 안된다. 이것은 클라이언트를 방해하지 않고, 더 이상 필요하지 않은 헤더의 #include 문구를 제거할 수 있도록 한다. 이것은 관련된 헤더 파일들에도 적용된다. - 만약 foo.cc에서 사용하는 기호가 bar.h로부터 왔다면, 비록 foo.h가 bar.h를 포함한다 할지라도 foo.cc는 bar.h를 포함(include) 해야 한다.

더보기
더보기

If a source or header file refers to a symbol defined elsewhere, the file should directly include a header file which properly intends to provide a declaration or definition of that symbol. It should not include header files for any other reason.

Do not rely on transitive inclusions. This allows people to remove no-longer-needed #include statements from their headers without breaking clients. This also applies to related headers - foo.cc should include bar.h if it uses a symbol from it even if foo.h includes bar.h.

Forward Declarations


가능하다면 전방 선언 사용을 피해라. 대신에 필요한 헤더 파일을 포함(include) 해라.

정의:

관련된 정의가 없는 독립체의 선언

// In a C++ source file:
class B;
void FuncInB();
extern int variable_in_b;
ABSL_DECLARE_FLAG(flag_in_b);

찬성 의견:

  • #includes는 컴파일러가 더 많은 파일들을 열게 하고 더 많은 입력들을 처리하게 만들기 때문에, 전방 선언은 컴파일 시간을 단축시킬 수 있다.
  • #includes는 헤더 파일에 있는 관련되지 않은 변화로 인해서, 코드가 더 자주 재 컴파일되게끔 하기 때문에, 전방 선언은 불필요한 재컴파일을 절약할 수 있다.

반대 의견:

  • 전방 선언은 종속성을 숨길 수 있으며, 헤더 파일들에 변화가 있을 때 필요한 재컴파일을 건너뛸 수 있다.
  • #includ의 상태를 반대하는 전방 선언은 자동화 툴이 기호를 정의하고 있는 모듈을 찾는 것을 어렵게 만든다. 
  • 이후 라이브러리의 변화에 의해 전방 선언은 깨질 수 있다. 함수와 템플릿의 전방 선언은 헤더의 주인이 인자의 타입을 넓히는 것, 기본값으로써 템플릿 인자를 추가하는 것, 새로운 네임스페이스로 이주하는 것 등 다른 호환이 가능한 API로의 변경을 하지 못하도록 막을 수 있다.
  • std:: 네임스페이스로부터의 기호를 전방 선언하는 것은 정의되지 않은 동작을 생산하는 것이다.
  • 전방 선언과 전체적인 #include 중 어떤 것이 필요한 것인지 알아내는 것은 까다롭게 될 수 있다. #include를 전방 선언으로 교체하는 것은 코드의 의미를 조용히 바꿀 수 있다:
// b.h:
struct B {};
struct D : B {};

// good_user.cc:
#include "b.h"
void f(B*);
void f(void*);
void test(D* x) { f(x); }  // calls f(B*)

      만약 #include가 전방 선언 B D(으)로 바뀌었다면, test() f(void*)를 호출할 것이다.

  • 헤더 파일에서 여러 기호의 전방 선언은 간단한 #include를 하고 있는 헤더 파일보다 더 장황해질 수 있다.
  • 전방 선언이 사용 가능하게 하는 구조(예를 들어, 객체 멤버 대신에 포인터 멤버를 사용하는 것)를 짜는 것은 코드가 더 느려지고 더 복잡하게끔 만들 수 있다.

결정:

다른 프로젝트에 정의된 독립체의 전방 선언을 하는 것을 피하도록 노력해라.

더보기
더보기

Avoid using forward declarations where possible. Instead, include the headers you need.

Definition:

A "forward declaration" is a declaration of an entity without an associated definition.

// In a C++ source file:
class B;
void FuncInB();
extern int variable_in_b;
ABSL_DECLARE_FLAG(flag_in_b);

Pros:

  • Forward declarations can save compile time, as #includes force the compiler to open more files and process more input.
  • Forward declarations can save on unnecessary recompilation. #includes can force your code to be recompiled more often, due to unrelated changes in the header.

Cons:

  • Forward declarations can hide a dependency, allowing user code to skip necessary recompilation when headers change.
  • A forward declaration as opposed to an #include statement makes it difficult for automatic tooling to discover the module defining the symbol.
  • A forward declaration may be broken by subsequent changes to the library. Forward declarations of functions and templates can prevent the header owners from making otherwise-compatible changes to their APIs, such as widening a parameter type, adding a template parameter with a default value, or migrating to a new namespace.
  • Forward declaring symbols from namespace std:: yields undefined behavior.
  • It can be difficult to determine whether a forward declaration or a full #include is needed. Replacing an #include with a forward declaration can silently change the meaning of code:
// b.h:
struct B {};
struct D : B {};

// good_user.cc:
#include "b.h"
void f(B*);
void f(void*);
void test(D* x) { f(x); }  // calls f(B*)

      If the #include was replaced with forward decls for B and D, test() would call f(void*).

  • Forward declaring multiple symbols from a header can be more verbose than simply #includeing the header.
  • Structuring code to enable forward declarations (e.g., using pointer members instead of object members) can make the code slower and more complex.

Decision:

Try to avoid forward declarations of entities defined in another project.

Inline Functions


함수가 작거나 10줄 미만일 때, 함수를 인라인으로 정의해라.

정의:

컴파일러가 일반적인 함수 호출 방식으로써 함수를 호출하기보다는 인라인으로 확장할 수 있도록 하는 방법으로 함수를 선언할 수 있다.

찬성 의견:

인라인 된 함수가 작을수록 인라인 함수는 더욱 효과적인 객체 코드를 만들 수 있다. 인라인 접근자, 뮤테이터 그리고 다른 짧은 코드, 성능에 크리티컬 한 함수들을 자유롭게 사용하라.

반대 의견:

인라인 함수를 과하게 사용하는 것은 실제적으로는 프로그램을 느리게 만든다. 함수의 크기에 의존해서 인라인 함수로 사용하는 것은 코드의 크기가 커지거나 작아지는 것을 야기할 수 있다. 매우 큰 함수가 환상적으로 코드의 크기를 증가시킬 수도 있는 반면에, 매우 작은 접근자 함수는 보통 코드의 크기를 감소시킨다. 현대의 프로세서는 명령 캐시를 더 잘 사용하기 때문에 보통 더 작은 크기의 코드를 실행시키고 더 빠르다. 

결정:

함수가 10 라인보다 많으면 인라인화 하지 않는 것이 꽤 괜찮은 경험적 규칙이다. implicit 된 멤버와 기본 소멸자의 호출로 인해 처음 생긴 때보다 더 긴 소멸자와 에 주의해라.

또 다른 괜찮은 경험적 규칙: 반복문과 스위치의 인라인 함수는 전형적으로 비용에 대해서 효과적이지 않다(일반적인 상황에서 반복문이나 스위치가 실행되지 않는 경우).

함수가 비록 일반적으로 선언되었다 할지라도, 항상 인라인 되지는 않다는 것은 중요하다; 예를 들어, 가상 함수 재귀 함수는 보통 인라인 되지 않는다. 보통 재귀 함수는 인라인 되지 않아야 한다. 가상 함수를 인라인 하려는 주된 이유는 정의를 클래스에 놓기 위해서 이거나, 편의를 위해서 혹은 행위를 문서화하려는 이유이다. 예를 들어, 접근자 그리고 뮤테이터가 있다.

더보기
더보기

Define functions inline only when they are small, say, 10 lines or fewer.

Definition:

You can declare functions in a way that allows the compiler to expand them inline rather than calling them through the usual function call mechanism.

Pros:

Inlining a function can generate more efficient object code, as long as the inlined function is small. Feel free to inline accessors and mutators, and other short, performance-critical functions.

Cons:

Overuse of inlining can actually make programs slower. Depending on a function's size, inlining it can cause the code size to increase or decrease. Inlining a very small accessor function will usually decrease code size while inlining a very large function can dramatically increase code size. On modern processors smaller code usually runs faster due to better use of the instruction cache.

Decision:

A decent rule of thumb is to not inline a function if it is more than 10 lines long. Beware of destructors, which are often longer than they appear because of implicit member- and base-destructor calls!

Another useful rule of thumb: it's typically not cost effective to inline functions with loops or switch statements (unless, in the common case, the loop or switch statement is never executed).

It is important to know that functions are not always inlined even if they are declared as such; for example, virtual and recursive functions are not normally inlined. Usually recursive functions should not be inline. The main reason for making a virtual function inline is to place its definition in the class, either for convenience or to document its behavior, e.g., for accessors and mutators.

Names and Order of Includes


헤더 include는 다음과 다음과 같은 순서로 되어야 한다: 관련된 헤더, C 시스템 헤더, C++ Standard 라이브러리 헤더, 기타 라이브러리의 헤더, 프로젝트의 헤더들.

모든 프로젝트의 헤더 파일은 유닉스 디렉터리의 별명자 . (현재 디렉터리) 또는 .. (상위 디렉터리)를 사용하지 않는 프로젝트 소스 디렉터리의 내림차순으로써 정렬되어야 한다. 예를 들어, google-awesome-project/src/base/logging.h는 다음을 반드시 include 해야 한다.

#include "base/logging.h"

dir2/foo2.h에서 구현이나 테스트가 주목적인 dir/foo.cc 또는 dir/foo_test.cc에서는 다음과 같은 순서로 include해라.

  1. dir2/foo2.h.
  2. 빈 라인
  3. C 시스템 헤더 (더 정확하게는: .h 확장자를 <>로 감싼다.), 예, <unistd.h>, <stdlib.h>.
  4. 빈 라인
  5. C++ Standard 라이브러리 헤더 (확장자 없이), 예, <algorithm>, <cstddef>.
  6. 빈 라인
  7. 다른 라이브러리의 .h 파일.
  8. 프로젝트의 .h 파일.

각각의 그룹은 빈 라인으로써 분리해라.

우선적 순서를 사용함으로써, dir2/foo2.h 파일과 관련된 헤더 파일을 누락시켰다면, dir/foo.cc 또는 dir/foo_test.cc의 빌드는 실패할 것이다. 따라서, 이 규칙은 다른 패키지의 무고한 다른 사람들보다 해당 파일로써 일하는 사람들에게 먼저 보여줄 수도록 보장할 수 있다.

dir/foo.cc 파일과 dir2/foo2.h 파일은 보통 같은 디렉터리에 있지만(예, base/basictypes_test.cc 그리고 base/basictypes.h), 가끔은 다른 디렉터리에 있을 수도 있다.

stddef.h 파일과 같은 C 헤더 파일은 기본적으로 C++ 헤더 파일(cstddef)과 상호 호환될 수 있다는 것을 알아둬라. 두 스타일이 허용되지만, 기존 코드와의 일관성을 선호한다.

각각의 include 섹션은 알파벳 순서로 정렬되어야 한다. 이전 코드는 이 규칙을 수용하지 않을 수 있으며, 적절한 때에 수정되어야 한다는 것을 알아둬라.

예를 들어, google-awesome-project/src/foo/internal/fooserver.cc의 include 방식은 아래와 같이 표현할 수 있을 것이다:

#include "foo/server/fooserver.h"

#include <sys/types.h>
#include <unistd.h>

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/server/bar.h"

예외:

때로는, 시스템별 코드는 조건부의 inlucde가 필요하다. 몇몇 코드는 다른 inlucde 후에 조건부의 include를 넣을 수 있다. 물론, 시스템별 코드는 작고 지역적으로 유지해라. 예:

#include "foo/public/fooserver.h"

#include "base/port.h"  // For LANG_CXX11.

#ifdef LANG_CXX11
#include <initializer_list>
#endif  // LANG_CXX11
더보기
더보기

Include headers in the following order: Related header, C system headers, C++ standard library headers, other libraries' headers, your project's headers.

All of a project's header files should be listed as descendants of the project's source directory without use of UNIX directory aliases . (the current directory) or .. (the parent directory). For example, google-awesome-project/src/base/logging.h should be included as:

#include "base/logging.h"

In dir/foo.cc or dir/foo_test.cc, whose main purpose is to implement or test the stuff in dir2/foo2.h, order your includes as follows:

  1. dir2/foo2.h.
  2. A blank line
  3. C system headers (more precisely: headers in angle brackets with the .h extension), e.g., <unistd.h>, <stdlib.h>.
  4. A blank line
  5. C++ standard library headers (without file extension), e.g., <algorithm>, <cstddef>.
  6. A blank line
  7. Other libraries' .h files.
  8. Your project's .h files.

Separate each non-empty group with one blank line.

With the preferred ordering, if the related header dir2/foo2.h omits any necessary includes, the build of dir/foo.cc or dir/foo_test.cc will break. Thus, this rule ensures that build breaks show up first for the people working on these files, not for innocent people in other packages.

dir/foo.cc and dir2/foo2.h are usually in the same directory (e.g., base/basictypes_test.cc and base/basictypes.h), but may sometimes be in different directories too.

Note that the C headers such as stddef.h are essentially interchangeable with their C++ counterparts (cstddef). Either style is acceptable, but prefer consistency with existing code.

Within each section the includes should be ordered alphabetically. Note that older code might not conform to this rule and should be fixed when convenient.

For example, the includes in google-awesome-project/src/foo/internal/fooserver.cc might look like this:

#include "foo/server/fooserver.h"

#include <sys/types.h>
#include <unistd.h>

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/server/bar.h"

Exception:

Sometimes, system-specific code needs conditional includes. Such code can put conditional includes after other includes. Of course, keep your system-specific code small and localized. Example:

#include "foo/public/fooserver.h"

#include "base/port.h"  // For LANG_CXX11.

#ifdef LANG_CXX11
#include <initializer_list>
#endif  // LANG_CXX11

 

중간 마무리


여러 명이 하나의 애플리케이션에서 다양한 기능을 나눠서 개발을 할 때, 정해진 코딩 스타일 없이 개발을 하다 보니 각자가 추구하는 스타일로 개발을 했다. 그런데 아무리 기능을 나눠서 개발했다 할지라도 서로의 코드를 읽어야 할 때가 있었는데, 이때 서로의 스타일이 다르다 보니 이해하는 데에 난해한 부분이 있었다. 아마 이때부터 코딩 스타일에 대해서 고민했던 것 같다.

내가 작성한 코드는 해당 코드를 읽고 유지보수 혹은 추가 개발을 할 다음 사람과의 대화라고 생각한다(아직까지 그 사람은 거의 대부분 나이긴 하다.). 그런데 대화를 함에 있어서 일관된 규칙이 있다면 그것을 이해하는 데에 들이는 시간보다 다른 것에 더 많은 시간을 투자할 수 있을 것이다.

Google은 내가 근무했던 회사들보다는 확실히 큰 집단인 것 같다(명확하다.). 수 억 라인의 코드와 수 천명의 엔지니어. 아무래도 거대한 집단이다 보니 일관된 코딩 규칙이 반드시 필요했을 것이다. 그 거대한 집단은 대체 어떠한 코딩 규칙을 정해놓았는지 훑어보고, 내가 작성한 코드를 읽을 나 자신을 위해서 나에게 적용할 수 있도록 노력해봐야겠다.

  • 헤더 파일은 독립적이어야 한다: 코드에서 사용하는 내용이 담긴 헤더 파일을 실제로 include 해야 한다.
  • 헤더 파일은 충돌을 피하기 위해서 반드시 헤더가드를 포함해야 한다.
  • 전방 선언을 사용하지 말라.
  • 함수가 10라인 미만일 때만 인라인 함수를 사용해라.
  • 헤더 파일 include는 순서가 정해져 있다.

C++ 개발을 하며 VSCode, cpplint 이 두 개를 사용하면서 사실 아주 자동적으로 지켜지는 내용들이었다. 특히, 헤더 파일 선을 하는 데 있어서 자유롭게 선언 후 lint를 적용하면 헤더 파일 순서를 정렬해줬기 때문에 이에 대해서는 고민을 해본 적이 없었던 것 같다. 아주 당연스럽게 정렬된 것들만 봐와서 그런 것 같다. 아울러, 전방 선언을 처음 접했던 때가 생각난다. include 하고 있지 않지만 빌드 시에 가져와서 사용한다는 게 참 신기했었는데 이 문법은 그냥 신기하다까지만 생각하고 사용하지 않도록 해야겠다.

 

참조URL

https://google.github.io/styleguide/cppguide.html

 

 

728x90