C++/Google C++ Style Guide

Functions

ukjin 2025. 5. 25. 22:09
반응형

Inputs and Outputs

C++ 함수의 Outputs은 일반적으로 return 값으로 제공되며, 가끔 Output parameter를 통해 제공된다(or in/out parameters).

output parameter를 보다 return value를 사용하는 것을 선호하자: 이것은 더 읽기 쉽고, 같거나 더 좋은 성능을 제공한다. See TotW#176

 

abseil / Tip of the Week #176: Prefer Return Values to Output Parameters

Tip of the Week #176: Prefer Return Values to Output Parameters Originally posted as TotW #176 on March 12, 2020 By Etienne Dechamps Updated 2020-04-06 Quicklink: abseil.io/tips/176 The problem Consider the following: // Extracts the foo spec and the bar s

abseil.io

return value를 선호하고 실패할 경우 return by reference를 선호7하자. null이 될 수 있지 않으면, raw pointer는 피하자.

Parameter는 함수로의 input 또는 함수로부터의 output, 또는 둘다이다. Non-optional input parameter 주로 value 또는 const reference로 사용되는데, non-optional output 과 input/output parameters는 주로 reference로 되어야만 한다(null이 아닌). 일반적으로, 선택적으로 by-value input를 표현하기 위해 std::optional를 사용하고, non-optional 형태가 reference를 사용했을 때 const pointer를 사용하자. non-const pointer는 optional output과 optional input/output parameter를 표현하기 위해 사용하자.

호출 이후까지 사용할 reference parameter를 필요로 하는 함수 정의는 피하자. 몇 경우, 일시적으로 연결될 수 있고, lifetime bug를 유발한다. 그 대신 lifetime requirement를 제거하는 방식예를 들어, paramter를 복사로 전달)를 찾거나, 유지되는 parameter를 포인터로 전달하면 lifetime과 non-null requirement를 명시하자. Totw116를 참고하자.

함수 파라미터 순서의 경우, 모든 input-only parameters는 특정 output parameter 전에 둔다. 특히, 단지 새롭게 추가된다는 이유만으로 새로운 파라미터를 끝에 추가하지 말자; 새로운 input-only parameter는 output paramter 전에 와야만 한다. 이것은 hard-and-fast 규칙(반드시 지켜야만 하는 규칙)이 아니다. input and output 인 parameter는 상황을 혼란스럽게 만들 수 있으며, 관련 함수들과의 일관성을 맞추기 위해서는 이 규칙을 약간 어길 수도 있다. 또한 가변 인자를 사용하는 함수는 특이한 파라미터 순서를 요구할 수도 있다.

Write Short Functions

작고 집중된 function을 선호하자.

긴 함수가 가끔 적절한 경우도 있는 것을 알고 있기에, 함수 길이에 hard-limit을 두지는 않는다. 만약 함수가 40줄 이상이라면 프로그램의 구조를 변경시키지 않고 분해할 수 있는지를 고려하자.

심지어 긴 함수가 적절히 동작 중 일지라도, 새로운 행동을 추가하기 위해 누군가는 몇달동안 수정한다. 이것은 찾기 어려운 버그를 낳는다. 다른 사람이 읽고 수정하기 쉬운 코드로 단순하고 짧게 함수를 유지하자. 작은 함수는 테스트하기도 쉽다.

몇몇 코드에서 길고 복잡한 함수를 발견한다면, 기존 코드를 수정하기를 망설이지 말자. 이러한 함수로 동작하는 것이 어렵다고 증명되거나, 디버깅하기 어렵다고 생각하거나 다른 context의 일부를 사용하고 싶다면, 함수를 더 작고 관리하기 쉬운 조각으로 쪼개는 것을 고려하자.

Function Overloading

overload가 호출되는 곳을 정확히 찾을필요 없이 호출 위치를 보고 있는 reader가 무슨 일이 일어나는 지 알 수 있을 경우에만 overload된 함수를 사용하자.

Definition:

const std::string 를 취하는 함수와 그것을 const char*로 overload하는 함수를 작성할 수도 있다. 하지만 이 경우 대신 std::string_view를 고려하자.

class MyClass {
 public:
  void Analyze(const std::string &text);
  void Analyze(const char *text, size_t textlen);
};

Pros:

Overloading은 동일한 이름의 함수가 다른 인자를 취하여 코드를 더 직관적으로 만든다. templatized code에서는 필수적일 수도 있으며, Visitors를 더 편리하게 만들 수 있다. const 또는 ref qualification에 따른 overloading은 코드를 더 사용성있고 효율적으로 만들 수 있다. 더 자세한 내용은 TotW #148을 참고하자.

Cons:

만약 단일 인자로 overload된 함수가 있으면, reader는 무슨 일이 일어나는 지 파악하기 위해 C++의 복잡한 매칭 규칙을 이해해야만 할지도 모른다. 또한 많은 사람들이 만약 파생 클래스가 함수의 일부 변형만 override하는 경우 상속의 symantic으로 혼란을 겪는다. 

Decision:

variants 간의 symantic이 다르지 않다면, 함수를 overload 할수도 있다. 이러한 overload는 types, qualifiers, 인자 개수에 따라 다를 수 있다. 하지만, 이러한 호출에서 reader는 overload set의 어느 멤버가 선택되었는지 알 필요가 없어야만 한다. 헤더에 한 줄의 코멘트로 이러한 overload 집합의 모든 entity를 작성할 수 있다면 잘 설계된 overload set의 좋은 신호다.

Default Arguments

Default arguments는 default가 항상 같은 값을 가지는 것을 보장할 때 non-virtual functions에서 허용된다. function overloading처럼 같은 제약 사항을 따르며, 만약 default arguments로 얻은 가독성이 아래 단점을 압도할만큼 좋지 못하면, overload된 함수를 선호하자.

Pros:

주로 default value를 사용하는 함수를 가지지만, 가끔 default를 재정의 하고 싶은 경우가 있다. Default parameter는 드물게 예외 처리를 위한 많은 함수를 정의할필요 없이 쉬운 방식으로 가능하게 한다. overloading function과 비교해, default arguments는 더 깔끔한 syntax를 가지며, 더 적은 boilerplate와 'required'와 'optional' argument 간 명확한 구별을 가진다.

Cons:

Default arguments는 overload된 function의 sematics을 다른 방식으로 달성하며, 결국 function을 overload 하지 말아야 하는 이유도 동일하게 적용된다.

virtual function에서 argument의 default는 target object의 static type으로 결정되며 주어진 function의 모든 overrides에 같은 deafult가 선언되는 것을 보장하지 않는다.

Default parameters는 각각 호출 위치에서 재평가 되며, 생성된 코드를 부풀린다. Readers는 각 호출에서 변하는 것 대신 선언 시점에 default 값이 고정되기를 기대할 수도 있다.

Function pointers는 default argument의 존재를 혼란스럽게 한다. 왜냐하면 fucntion signature은 주로 호출 signature와 일치하지 않기 때문이다. function overload 추가는 이러한 문제를 피한다.

Decision:

Default arguments는 virtual function에서는 금지되며, 이들은 적절히 동작하지 않고, 언제 평가되는지에 따라 같은 값으로 평가되지 않는다.(예를 들어, void f(int n = counter++);으로 작성하지말자)

몇몇 다른 경우, default arguments는 위의 약점을 충분히 극복하여 function declarations의 가독성을 향상시킬 수 있는데, 이 경우 허용된다. 고민되면, 그냥 overloads 사용하자.

Trailing Return Type Syntax

일반적인 syntax를 사용하는 것이 비현실적이거나, 가독성이 떨어지는 경우에만 trailing return type을 사용하자.

Definition:

C++은 함수 선언의 두가지 형식을 허용한다. 옛날 형식에서 return type은 function name 전에 나타난다. 예를 들어,

int foo(int x);

새로운 형식은 function name 앞에 auto 키워드를 사용하고 argument list 이후 trailing return type을 사용한다. 예를 들어, 위 선언과 동일하게 쓰였다:

auto foo(int x) -> int;

trailing return type은 function의 scope 에서 존재한다. 이는 int 같은 단순한 경우에는 차이점이 없지만, class scope에서 선언된 type 또는 function paramter의 측면에서 쓰여진 type의 경우 더 복잡한 경우 문제가 있다.

Pros:
Trailing return types은 단지 명시적으로 lambda expression의 return type을 명시한다. 몇 가지 경우에서 compiler는 lambda의 return type을 추론할 수 있지만 모든 경우에서 그렇지는 않다. 심지어 compiler가 자동으로 추론할 수 있을 경우, 가끔 명시적으로 그것을 구체화하는 것은 reader에게 더 명확하다.

가끔 이미 드러난 function의 parameter 이후 return type을 구체화하는 것은 더 읽기 쉽다. 이는 특히 template parameter에 의존하는 return type일 때 더 그렇다. 예를 들어,

template <typename T, typename U>
auto add(T t, U u) -> dcltype(t + u);

versus

template <typename T, typename U>
decltype(declval<T&>() + declval<U&>()) add(T t, U u);

Cons:

Trailing return type synctax는 상대적으로 새롭고 C와 Java 같은 언어처럼 C++에서 analogue가 없기에 몇몇 reader는 친숙하지 않다.

기존 코드 베이스에는 새로운 syntax의 사용으로 변경되지 않을 함수 선언이 매우 많으며, 현실적인 선택은 옛날 syntax만 사용하거나 두 가지를 혼용해서 사용하는 것이다. 스타일을 통합하기에 한가지 버전만 사용하는 것이 더 낫다.

Decicion:
대부분의 경우, function name 전에 return type이 오는 옛날 스타일의 function 선언을 계속해서 사용한다. 새로운 trailing-return-type 형식은 필요한 경우에만 (lambdas 같은) 또는 function의 paramter list 이후 type을 둠으로 써 더 가독성 좋게 타입을 작성하기 위해서 가능하다. 후자는 매우 희귀해야만 한다; 대부분의 복잡한 template code에서 있는 이슈이며, discouraged in most case이다.

반응형

'C++ > Google C++ Style Guide' 카테고리의 다른 글

Other C++ Features  (0) 2025.05.26
Google-Specific Magic  (0) 2025.05.26
Classes  (0) 2025.05.20
Scoping  (0) 2025.05.15
Header Files  (0) 2025.05.15