본문 바로가기

C++ & etc

[Effective C++] Chapter 01. C++ 문법을 따르자

Const를 적극적으로 활용하자

define 보다 const, inline 을 사용하도록 하자

#define ASPECT\_RATIO 1.653

위 형태라면 컴파일 하기 전에 ASPECT_RATIO 부분을 전부 1.653으로 대체해 버린다.

컴파일 에러가 났을 때, 1.653 이라는 숫자가 어디에서 왔는지 찾기 힘들 수 있다.

(특히 다른 문서에서 define된 매크로라면 더욱)

const double AspectRatio = 1.653

이렇게 const를 사용하는 것이 더 낫다. 문제가 있을 때 에러 메시지에 AspectRatio 가 나타난다.

틈나는 대로 const를 활용하자

한번 선언하고 변경할 일이 없다면, 그 변수는 const인 것이 낫다.

어떤 함수의 인자로 받은 변수를 변경할 일이 없다면(읽기만 한다면), 그 함수는 const인 것이 낫다.

  • const 함수에서도 예외적으로 변경할 수 있도록 mutable 변수를 선언할 수도 있다.
class CTextBlock {
 public:
 ...
     std::size_t length() const;
 private:
     char *pText;
     mutable std::size_t textLength;
     mutable bool lengthIsValid;
 };

 std::size_t CTextBlock::length() const
 {
     if (!lengthIsValid) {
         textLength = std::strlen(pText);
         lengthIsValid = true;
         }
     return textLength;
 }

const 함수에서 textlength, lengthIsValid 를 변경했지만, mutable로 선언했기 때문에 문제가 없다.

객체를 사용하기 전 반드시 초기화하자

일반 객체의 초기화

초기화하지 않은 객체를 읽는 것은 undefined behavior를 낳는다. 이것은 디버깅하기 매우 힘들다.

멤버 변수는 클래스 생성자 본문이 불리기 전에 초기화되어야 한다.

아래는 초기화가 아닌 대입(assignment) 이다.

 ABEntry::ABEntry(const std::string& name, const std::string& address,
 const std::list<PhoneNumber>& phones)
 {
     theName = name;
     theAddress = address;
     thePhones = phones;
     numTimesConsulted = 0;
 }

아래와 같이 초기화하는 것이 C++에서 권장되는 형태이다.

 ABEntry::ABEntry(const std::string& name, const std::string& address,
 const std::list<PhoneNumber>& phones)
     :theName(name),
        theAddress(address),
        thePhones(phones),
        numTimesConsulted(0)
 {};

대입을 해서 복사 생성자가 호출되는 상황은 낭비이므로, 생성자 앞에서 미리 초기화하는 것이 좋다.

아무 값도 넣지 않고 기본값으로 초기화하고 싶은 경우, 아래와 같이 해도 된다.

 ABEntry::ABEntry()
 : theName(),
     theAddress(),
     thePhones(),
     numTimesConsulted(0)
 {}

객체가 초기화 되는 순서는 아주 복잡하므로, 선언 된 순서를 지키면서 초기화 하는 것이 여러모로 좋다. (초기화 하는 순서와 상관없이 변수가 선언된 순서로 초기화되기 때문)

Static 객체의 초기화

여기에서 객체란, 변수, 또는 변수로서 정의된 어떤 클래스의 인스턴스를 의미한다.

정적 객체(Static 객체)에 대한 설명은 아래와 같다.

더보기

static 변수와 static 함수

static 변수는 모든 인스턴스에서 공통된 값, 단 1개의 값만을 공유하는 변수이다. 각 인스턴스에서 개별적으로 수정할 수 없고, 메모리 공간에 항상 1개가 존재한다. static 변수는 static 함수로만 접근 및 변경이 가능하다.

static 변수는 한번 생성되면 프로그램이 끝날 때 까지 유지된다.

static 함수는 클래스 인스턴스 없이도 호출할 수 있다. 만약 일반 함수라면

ClassName cn; cn.Function();

같은 형태로 사용하겠지만, static 함수는

ClassName::StaticFunction();

형태로 호출한다.

static 함수는 클래스 안의 어떤 변수도 변경하지 않고, 사용하지도 않는 함수이다.

이 클래스 안의 변수들과 관련 없지만, 이 클래스의 카테고리 안에 속하게 하고 싶은 함수일 경우 static으로 선언한다. 즉 클래스 바깥에 선언되어도 컴파일은 잘 되지만, 맥락상 이 클래스 안으로 묶고 싶을 때..

static이라고 명시된 객체, 클래스 바깥에 전역으로 선언된 객체 등이 여기에 포함된다.

static 객체를 초기화 하기 전에 참조하는 것을 막기 위해서는, 직접 이 객체를 참조하는 대신 같은 이름의 Get 함수를 만든다. 이 함수 안에서 static 으로 생성해 준 뒤, 이것을 return 하는 것이다.

FileSystem 안의 numDisks() 함수를 호출한다고 할 때,

// 이 형태 대신
FileSystem& tfs;

tfs.numDisks();

// 아래 형태를 사용한다
FileSystem& tfs()
 {
	 static FileSystem fs;
	 return fs;
 }
 
 tfs().numDisks();

 

이렇게 함수화 하는 것…

이런 형태를 Singleton 패턴이라고 한다. 게임인스턴스, subsystem 등 인스턴스가 메모리가 단 하나만 존재하고, 이것을 시스템 전체에서 공용으로 사용하고 싶을 때 유용한 패턴이다.

이렇게 해 두면, 메모리에 한번 생성된 객체를 필요할 때 마다 얻어오기만 하면 된다. 한번 만들어졌다면 또다른 인스턴스를 계속 생성할 필요가 없다.

ClassName::GetInstance() 와 같은 static 함수 형태로 인스턴스를 얻도록 되어 있다면, 이 클래스는 싱글턴 패턴으로 만들어졌음을 짐작할 수 있다.

 

https://velog.io/@jhbae0420/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0%EC%99%80-%EB%AC%B8%EC%A0%9C%EC%A0%90

 

싱글톤 패턴의 사용 이유와 문제점

싱글톤 패턴은 특정 클래스의 인스턴스를 1개만 생성되는 것을 보장하는 디자인 패턴이다. 즉, 생성자를 통해서 여러 번 호출이 되더라도 인스턴스를 새로 생성하지 않고 최초 호출 시에 만들어

velog.io