Programming/C++

[Effective C++] Chapter 1. C++에 왔으면 C++의 법을 따릅시다.

며용 2022. 2. 15. 21:36

항목 1: C++를 언어들의 연합체로 바라보는 안목은 필수

 

C++Multiparadigm Programming Language다.

  • C
  • Object Oriented C++
  • Template C++
  • STL

C++?

절차지향 C → 객체지향 기능(클래스, 캡슐화, 추상화, 상속, 다형성)을 추가 → 템플릿(일반화), STL 추가

 

==> 하나로 정해진 언어가 아니라서 선택한 언어 규칙에 맞춰야함

       (C++: call by reference / STL은 C 포인터를 본따 만들었기 때문에 call by value 사용하기)

 

(템플릿 관련 참고: https://www.youtube.com/watch?v=a6BQphLoTag) 

 


항목 2: #define을 쓰려거든 const, enum, inline을 떠올리자

 

선행처리자(#define)보다 컴파일러(const, enum, inline)를 더 가까이 하자.

 

#define은 symbol table에 올라가지 않아 추적이 불가, 디버깅이 힘들다.

(symbol table: 변수의 의미를 추적하기 위해 컴파일러에서 생성 및 유지 관리하는 중요한 데이터 구조)

(즉, 범위에 대한 정보, 변수 및 함수 이름, 클래스, 객체와 같은 다양한 엔티티의 인스턴스에 대한 정보를 저장)

 

(enum과 달리 숫자 상수로 되어있어 어디에서 컴파일 에러가 발생했는지 알기 힘들다.)

 

const, enum은 여러 번 사용하더라도 한 개의 사본만 생긴다.

선행처리자, 매크로는 여러 개의 사본이 생성된다. 

(선행처리자는 컴파일 단계에서 #define이 사용되는 곳에 동일한 코드들이 복사된다.)

(때문에 바이너리 파일 사이즈도 증가함)

 

  • 상수 타입: const 붙이기

 

  • 클래스 상수는 #define으로 정의하면 유효범위가 모호해짐(컴파일 끝날 때까지), 캡슐화도 안됨
    • static const double Factor = 1.5; 
       클래스 상수는 static으로 선언, 선언부에서 초기화 하기
    • 멤버 배열 선언 사이즈를 변수로 받게 하는 방법 --> enum hack

 

  • 매크로 함수 대신 inline 템플릿 사용 
    • 장점: 클래서 유효범위, 접근 규칙 적용

 

 


항목 3: 낌새만 보이면 const를 들이대 보자!

 

함수 뒤에 const를 붙이는 경우?

  • 어떤 함수가 객체를 변경시키고, 시키지 않는지 한 눈에 알 수 있게 해줌
  • 상수 객체를 사용할 수 있게 함.

 

int value = 0;
int temp = 12;

///////////////////////////////////
//포인터가 가리키는 공간이 const
const int* ptr = &value;

*ptr = 10; //ERROR; 주소값은 const로 변경 불가
value = 1; 
ptr = &temp; //가리키고 있는 값은 바꿀 수 없지만 주소솟값은 바꿀 수 있음 (const int(O)임 const int*(X))

///////////////////////////////////
//상수 포인터
int* const const_ptr = &value;

*const_ptr = 10; //공간은 const가 아니기에 가리키는 값 변경 가능
const_ptr = &temp; //ERROR; 주소 변경 불가

///////////////////////////////////
//상수를 가리키는 상수 포인터
const int* const cptr = &value;

*cptr = 10; //ERROR
cptr = &temp; //ERROR

///////////////////////////////////
//함수에 사용하는 cosnt

class CClass
{
private:
	int m_num;
    int* p_ptr;
public:
	int foo(const int num){} //함수 내에서 파라미터 수정 방지
	void print() const //const 멤버함수; const 객체는 const멤버 함수만 호출 가능
    {
    	m_num = 0; //ERROR 함수 내에서 멤버 변수 변경 불가
    }
	const int& get() //반환값이 const, 상수 레퍼런스; 반환된 값을 변경하지 못하도록
    {
    	return p_ptr; //상수 레퍼 변수만 받을 수 있음
    }
}
더보기

포인터 관련 예시 이미지

 

 

STL iterator

--> 포인터가 가리키는 대상 변경을 막기 위해서 const_iterator 활용하기

* iterator 앞에 const를 붙이는 것과 const_iterator를 붙이는 차이를 정확하게 알아야함

 

비트 수준 상수성?

컴파일러가 지켜주는, 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 변경하지 않는다. (대입연산)

--> const 객체여도 return char&인 경우 밖에서 수정될 수 있다.

--> mutable을 선언하여 값을 변경하는 것은 지양해야함.

 

const 유무로 오버로딩도 됨

     ㄴ const 멤버함수: 상수 객체에 대해 호출될 함수를 뜻함

--> 상수/비상수 버전 따로 제공하면서 중복 코드와 유지보수가 힘들어짐

--> const_cast 사용하자

ex) return const_cast<char&>(static_cast<const Object&>(*this)[value]);

 

 


항목 4: 객체를 사용하기 전에 반드시 그 객체를 초기화 하자

 

C++은 초기화가 될 수도 안 될 수도 있다 --> 초기화 필수 (초기화 != 대입)

(C스타일의 malloc 사용으로 객체 초기화 안됨. new 사용)

(https://en.cppreference.com/w/cpp/language/initialization : 초기화 관련 문서 읽어보자)

 

대입문이 아닌 멤버 초기화 리스트를 쓰자

Object::Object(const char& _name): name(_name) 
{ 
	
}

 

--> 대입은 초기화 후 값을 대입해서 두 번 실행되지만 초기화 리스트는 바로 필요한 값을 써버림. 효율적

(복사생성자와 대입연산자 개념이 헷갈릴 수 있으므로 정확하게 구분해야함)

(객체 초기화시 복사생성자 초기화 리스트를 사용해 초기화할 것)

 

 

전역/static 객체는 불확실한 초기화 순서를 염두해두자

--> 초기화 순서가 정해져 있지 않기 때문에 함수 내에서 선언을 해서 인스턴스를 get 해야한다.

--> 또는 singleton pattern을 사용하자

 

 

 

 

*refer:

https://blog.naver.com/kimyoseob/220753117724