Fogeaters, Light The World.

11

2017-Jun

[C++] 코딩시 좋은 습관들 : 스타일

작성자: title: MoonBlonix IP ADRESS: *.64.228.3 조회 수: 1574

출처 :: http://blog.naver.com/dev_kr/220865726956



스타일

 

스타일에 있어서 첫번째로 중요한것은 일관성입니다. 두번째로 중요한 것은 평균적으로 C++ 프로그래머들이 사용하는 스타일을 따라가는 것입니다.

C++은 임의적인 길이의 식별자 이름을 허용합니다. 그러므로 식별자 네이밍시에 "간결"에 집착할 필요는 없습니다. 기술적(descriptive)인 이름을 사용하고 일관성을 유지하세요. 


  • CamelCase
  • snake_case 
 

이 두 예제가 가장 흔한 스타일입니다. 


일반적인 C++ 네이밍 관습


  • 타입은 대문자로 시작 : MyClass
  • 함수와 변수는 소문자로 시작 : myMethod
  • 상수는 모두 대문자 : const double PI=3.14159265358979323;

C++ 표준 라이브리리(그리고 Boost같은 다른 잘 알려진 C++ 라이브러리)는 이 가이드라인을 따릅니다.


  • 매크로 이름은 대문자와 언더스코어 : INT_MAX
  • 템플릿 인자는 CamelCase : InputIterator
  • 그 외의 모든것은 snake_case : unordered_map 
 

private 멤버 데이터를 구분하세요.


private 데이터는 m_ 으로 시작하도록 네이밍하세요. 이는 public 데이터와 private 데이터를 구분지어 줍니다. m_의 뜻은 "member" 입니다. 


함수 인자를 구분하세요.


가장 중요한것은 당신의 코드 기반의 일관성입니다. 이는 당신의 일관성 유지에 도움이 될 가능성이 있습니다.

함수 인자를 t_ 로 시작하도록 네이밍하세요. t_ 는 "the" 라고 해석될수 있습니다만, 해석은 중요하지 않습니다. 이러한 네이밍은 일관성을 유지함과 동시에 함수 인자와 같은 스코프 내의 다른 변수를 구분하는 것을 도와줄 겁니다.


모든 식별자의 접두사나 접미사는 당신이 선택할 수 있습니다. 예:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Size
{
  int width;
  int height;
 
  Size(int t_width, int t_height) : width(t_width), height(t_height) {}
};
 
// 이 버전은 쓰레드 안전한 클래스의 구현이 될 수 있습니다.
// 하지만 중요한것은 우리가 데이터를 은닉해야 할 때도 있지만, 아닐때도 있다는 것입니다.
class PrivateSize
{
  public:
    int width() const { return m_width; }
    int height() const { return m_height; }
    PrivateSize(int t_width, int t_height) : m_width(t_width), m_height(t_height) {}
 
  private:
    int m_width;
    int m_height;
};
cs


어느 것도 _ 로 시작하게 네이밍하지 마세요.


만약 그런다면, 당신은 컴파일러나 표준 라이브러리가 예약해놓은 식별자 이름과 충돌하는 위험을 감수해야합니다.


http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier


잘 설계된 예제


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass
{
public:
  MyClass(int t_data)
    : m_data(t_data)
  {
  }
 
  int getData() const
  {
    return m_data;
  }
 
private:
  int m_data;
};

cs 


소스코드 밖 디렉토리에 빌드하세요.


생성된 파일이 소스코드가 있는 디렉토리와 구분된 output 폴더에 위치하도록 하세요.


nullptr를 사용하세요.


C++11에서 nullptr, null pointer이라는 특별한 값을 가지는 키워드가 소개되었습니다. 이는 기존의 null pointer로 사용하던 (void)0이나 기존 NULL에 대한 대체자가 됩니다.


주석


주석은 /* */ 대신 //를 사용하세요. //는 디버깅시에 코드를 주석화 하는데에 더 수월합니다.


1
2
3
4
// this function does something
int myFunc()
{
}
cs


만약 이 함수를 아예 주석화 해버리고 싶다면 이렇게 할테지만:


1
2
3
4
5
6
/*
// this function does something
int myFunc()
{
}
*/

cs 


이 방법은 함수 구현 코드에 /* */ 주석이 사용되었다면 오류가 발생합니다.


헤더에서 using namespace 를 사용하지 마세요.


헤더에서 using namespace를 하면 그 헤더를 포함시킨 모든 코드에 using namespace가 적용됩니다. 이러면 네임스페이스가 오염되며 이름 충돌의 원인이 됩니다. 단 구현 파일에 using namespace를 적는건 문제가 되지 않습니다.


포함(inlcude) 보호 


헤더파일은 같은 헤더를 복수 포함할때의 문제나 다른 프로젝트와의 충돌을 막기 위해 명확히 이름 붙여진 포함 보호를 포함해야 합니다.


1
2
3
4
5
6
7
8
9
#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP
 
namespace MyProject {
  class MyClass {
  };
}
 
#endif

cs 


또는 더욱 짧고 의도가 명백한 #pragma once를 써도 됩니다. 이는 표준은 아니지만, 거의 모든 컴파일러가 지원하는 반표준(quasi-standard) 구문입니다.

블럭에 {}를 쓰세요.

중괄호를 사용하지 않는다면 의미 전달에 오류가 있을수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 나쁨
// 오류가 없고 당신이 의도한대로 작동하지만, 혼란을 야기합니다. 
// 추후에 첨가 수정이 있으면 오류가 발생하므로, 세심한 주의가 필요합니다. 
for (int i = 0; i < 15++i)
  std::cout << i << std::endl;
 
// 나쁨
// 보이는 것과는 다르게 std::cout는 for 루프의 일부가 아닙니다.
int sum = 0;
for (int i = 0; i < 15++i)
  ++sum;
  std::cout << i << std::endl;
 
 
// 좋음
// 어떤 구문이 루프 또는 블럭에 포함되어있는지 명확합니다.
int sum = 0;
for (int i = 0; i < 15++i) {
  ++sum;
  std::cout << i << std::endl;
}

cs 


한 줄의 길이를 합리적이게 유지하세요.


1
2
3
4
5
6
7
8
9
10
11
// 나쁨
// 읽기 힘듭니다.
if (x && y && myFunctionThatReturnsBool() && caseNumber3 && (15 > 12 || 2 < 3)) {
}
 
// 좋음
// 논리 단위 그룹화로써 읽기 수월합니다.
if (x && y && myFunctionThatReturnsBool()
    && caseNumber3
    && (15 > 12 || 2 < 3)) {
}
cs


많은 프로젝트와 코딩 표준에서는 한줄에 80 또는 100자 미만의 코드길이를 유지하라는 가이드 라인이 있습니다. 이러한 코드는 일반적으로 읽기가 수월합니다.


로컬 헤더 포함엔 "" 를 사용하세요.


<> 는 ​시스템 헤더 포함을 위해서 예약되어 있습니다.​


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 나쁨
// 컴파일시 별도의 -I 지시자가 필요합니다.
// 또한 표준에도 맞지 않습니다.
#include <string>
#include <includes/MyHeader.hpp>
 
// 더 나쁨
// 더욱 구체적인 -I 지시자가 필요합니다.
// 또한 코드를 패키징하고 배포하기 어려워집니다.
#include <string>
#include <MyHeader.hpp>
 
 
// 좋음
// 별도의 컴파일 인수가 필요하지 않으며 유저에게 포함 파일이
// 로컬 파일임을 알릴 수 있습니다.
#include <string>
#include "MyHeader.hpp"
cs


멤버 초기화 리스트로 멤버 변수를 초기화 하세요.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 나쁨
class MyClass
{
public:
  MyClass(int t_value)
  {
    m_value = t_value;
  }
 
private:
  int m_value;
};
 
 
// 좋음 
// C++의 멤버 초기화 리스트는 
// 언어적인 면에서 독특합니다. 좀더 깔끔한 코드를 만들며 
// 잠재적인 성능적 이득까지 볼 수 있습니다.
class MyClass
{
public:
  MyClass(int t_value)
    : m_value(t_value)
  {
  }
 
private:
  int m_value;
};
cs



만약 C++11 이라면 클래스 선언부 안에 이렇게 할 수도 있습니다:


1
2
3
4
// ... //
private:
  int m_value = 0;
// ... //
cs


이렇게 하면 생성자가 절대로 초기화를 "까먹지" 않음을 명확히 해줍니다.


brace-initialization를 사용하면 컴파일 타임에 축소 변환을 허용하지 않게 해줍니다.


1
2
3
4
5
6
7
// 가장 좋음
 
// ... //
private:
  int m_value{ 0 }; // 허용
  unsigned m_value_2 { -1 }; // 컴파일 타임 오류, signed에서 unsigned로 축소 시도
// ... //
cs

{} 초기화를 쓰지 않을 강한 이유가 없으면 {} 초기화를 선호하세요.

멤버를 초기화 하지 않음은 undefined behavior의 근본적 원인이 될 수 있으며 이 오류는 아주 찾기 힘듭니다.

항상 네임스페이스를 사용하세요.

대부분의 상황에서 식별자를 전역으로 선언할 이유는 없습니다. 대신, 함수와 클래스는 적절하게 이름 붙여진 네임스페이스 안에 있어야 합니다. 전역에 선언된 식별자는 다른 라이브러리의 식별자와 충돌하는 문제를 일으킬수 있습니다(이러한 문제는 C에서 많이 발생하는데, 왜냐하면 C는 네임스페이스가 없기 때문입니다.)

표준 라이브러리에 대해 올바른 정수 타입을 사용하세요.

표준 라이브러리는 크기에 관한것은 대부분 std::size_t 를 사용합니다. size_t의 size는 implementation defined 입니다.

일반적으로 auto를 사용하면 대부분의 문제가 해결됩니다. 하지만 전부는 아닙니다.

올바른 정수 타입을 사용하고 C++ 표준 라이브러리와의 일관성을 유지하도록 하세요. 지금 사용하고 있는 플랫폼에선 경고가 없을 수 있지만, 플랫폼이 변경될시 경고가 날 가능성이 있습니다.

unsigned 값을 다룰때 integer underflow가 발생할 수 있음을 참고하세요. 예:
1
2
3
4
5
std::vector<int> v1{2,3,4,5,6,7,8,9};
std::vector<int> v2{9,8,7,6,5,4,3,2,1};
const auto s1 = v1.size();
const auto s2 = v2.size();
const auto diff = s1 - s2; // diff가 아주 큰 숫자로 underflow 합니다.
cs



파일 확장자에 .hpp와 .cpp를 사용하세요.


궁극적으로 이것은 설정의 문제인데, 대부분의 에디터와 툴들은 기본적으로 .hpp나 .cpp를 인식합니다.


대부분의 컴파일러에선 자동 생성한 파일이 .hxx와 .cxx 등의 확장자를 가집니다. 그렇다면 유저가 생성한 파일은 .hpp와 .cpp를 사용하세요. 자동 생성된 파일과 유저가 생성한 파일이 구분지어집니다. 이 두 종류의 파일을 구분짓는 것은 도움이 꽤나 많이 되는 중요한 작업입니다.


대표적인 예로 OpenStudio는 user-generated 파일에 대해선 .hpp 와 .cpp를 사용하고 tool-generated 파일엔 .hxx와 .cxx를 사용합니다. 둘다 문제 없이 인식됩니다.


탭과 스페이스를 섞지 마세요.


몇몇 에디터들은 기본적으로 인덴트를 탭과 스페이스를 섞어서 하는 경우가 있습니다.

이러한 인덴트 방식은 다른 인덴트 설정을 사용하고 있는 모든 사람들에게 코드 읽기를 아주 어렵게 만들어 버립니다. 이러한 일이 발생하지 않도록 설정하세요.


assert()안에 side-effect 를 가지는 코드를 넣지 마세요.


1
assert(registerSomeThing()); // registerSomeThing()이 반드시 true를 반환하도록 함
cs


위 코드는 디버그 모드에선 의도한 대로 작동하지만, 릴리즈 모드에선 컴파일러에 의해 assert 구문이 제거됩니다. 따라서 디버그 모드와 릴리즈 모드에서 프로그램이 각각 다른 동작을 하는 문제가 발생할 수 있습니다. 이는 assert 매크로가 릴리즈 모드에선 공백 코드로 치환되기 때문입니다.


템플릿을 두려워하지 마세요.


템플릿은 당신이 DRY 원칙을 지키도록 도와줍니다. 템플릿은 매크로보다 선호되어야 하는데, 이유는 여러가지 입니다. 예를 들어 매크로는 네임스페이스 개념이 없어 심각한 충돌 문제가 발생 할 수 있습니다.


연산자 오버로딩을 사려 깊게 사용하세요.


연산자 오버로딩은 보다 표현적(expressive)인 코드를 위해 생겨났습니다.

보통 두개의 정수를 더함은 a + b 로 표현되며, a.add(b)는 별로 실용적인 방법이 아닙니다. 또 다른 예제로는 std::string인데, 두개의 문자열을 연결시킴은 string1 + string2 로 표현됩니다.


당신은 옳지 못한 연산자 오버로딩을 통해 도저히 읽을수 없는 코드를 만들어 낼 수 있습니다. 연산자를 오버로딩할땐,

stackoverflow에 있는 3가지 기본 규칙을 참고하세요.


더욱 구체적으로, 이것들을 명심해두면 좋습니다:

  • 자원 관리시에 operator=()를 오버로딩함은 필수입니다. 또는 밑의 Rule Of Zero 를 참고하세요.
  • 다른 모든 연산자에 대해서는, 오버로딩 하려는 연산자의 본 의미와 오버로딩의 동작이 일치할때만 오버로딩하세요. 예를 들어 + 를 빼기 동작으로 오버로딩하면, 사용자에게 큰 혼란을 줍니다.
  • 항상 연산자 우선순위를 고려하고, 비직관적인 구조를 피하세요.
  • 수(數) 타입을 설계할때나 특정한 상황에서 명백히 의도가 드러나는 경우가 아니면 ~나 %같은 특수한 연산자를 오버로딩하지 마세요
  • 절대로 operator(), (콤마 연산자) 를 오버로딩하지 마세요.
  • 스트림을 다룰때는 비멤버 함수 operaotr>>() 그리고 operator<<()를 오버로딩하세요.
  • 일반적인 오버로딩 대상 연산자는 여기를 참조하세요.
더 많은 팁에 대해선 여기를 참조하세요.

Single Parameter 생성자

Single parameter 생성자는 자동으로 두 타입간 암묵적 변환을 적용하기 위해 사용됩니다.
이는 std::string(const char*) 같은 경우에 유용하게 사용 될 수 있으나 일반적으로 기피해야 하는데, 왜냐하면 이 변환 작업이 런타임 오버헤드에 영향을 미칠 수 있기 때문입니다.

대신 Single parameter 생성자를 explicit 키워드로 선언하면, 생성자를 명시적으로 호출함을 강제합니다.

변환 연산자

Single parameter 생성자와 비슷하게 변환 연산자는 컴파일러에 의해 호출되고, 의도되지 않은 오버헤드가 발생될 수 있습니다.
이것 또한 explicit로 선언하는게 좋습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
//bad idea
struct S {
  operator int() {
    return 2;
  }
};
 
//good idea
struct S {
  explicit operator int() {
    return 2;
  }
};
cs

Rule Of Zero

Rule Of Zero는 컴파일러가 제공하는 함수들(복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자, 소멸자)을 당신이 설계중인 클래스가 어떤 특별한 자원 관리 모델이 필요한 것이 아닌 이상 당신이 직접 구현할 필요는 없음을 말합니다.

이 규칙을 따른다면 컴파일러가 최적화된 버전을 제공할 수 있게 해주고, 멤버 변수가 나중에 별도로 추가되었을때 자동으로 보수해주는 등의 시간적 이득 또한 취할 수 있습니다.
profile
List of Articles
번호 제목 글쓴이 날짜 조회 수
공지 [Web] 클라우드 IDE + 2 title: MoonBlonix 2017-06-25 15126
91 [web] 제로보드처럼 url 줄이기 + 1 title: MoonBlonix 2017-08-04 1655
90 [php] 환경변수 $_SERVER title: MoonBlonix 2017-08-04 1686
89 [mysql] 테이블 수정 title: MoonBlonix 2017-08-04 1613
88 [Javascript] 섬세한 뒤로가기 구현 title: MoonBlonix 2017-08-01 1496
87 [php] 쿠키 사용하기 title: MoonBlonix 2017-08-01 1741
86 [javascript] POST 전송하기 title: MoonBlonix 2017-07-31 1605
85 [Web]다국적 웹사이트 제작 title: MoonBlonix 2017-07-27 1926
84 [php] 5 -> 7 변경점 정리 title: MoonBlonix 2017-07-23 1342
83 [php/mysqli] 설치 및 연동 + 2 title: MoonBlonix 2017-07-23 1691
82 [MySQL] 설치 및 기초명령어 title: MoonBlonix 2017-07-19 1007
81 [Web] JQuery 설치 title: MoonBlonix 2017-07-04 1673
80 [CSS] Toggle Switch Examples title: MoonBlonix 2017-07-01 1832
79 [CSS] Input Text Styles title: MoonBlonix 2017-07-01 1691
78 [Web] CSS 프론트엔드 워크프레임 소개 title: MoonBlonix 2017-06-25 1443
» [C++] 코딩시 좋은 습관들 : 스타일 title: MoonBlonix 2017-06-11 1574
76 [PHP] 강좌 모음 + 1 title: MoonBlonix 2017-06-08 1907
75 [Arduino] 아두이노로 GPS(위치) 추적기(GPS Tracker)를 만들어 보았다 + 1 2N 2017-03-06 1424
74 [AI]딥러닝 공부 가이드 (SW 준비편) title: MoonBlonix 2017-01-15 1501
73 [C++ STL] std::vector + 2 title: MoonBlonix 2016-12-14 1737