이 게시글은
서울대학교 데이터사이언스대학원
조요한 교수님의
데이터사이언스 응용을 위한 컴퓨팅 강의를
학습을 위해 재구성하였습니다.
이번 시간에는
c++에서 ₩class와
class template에 대해서
배운 내용에 대해서 정의해보려고 한다
우선 c++의 개발자
Bjarne Stroustrup의
Why I created C++
영상을 보면
c++을 왜 만들었는지에 대한
이야기가 나오는데
c에는 없는 class의 개념을
도입하기 위해서 만들었다고한다
Simula라는 언어에서
class의 개념을 가져온 뒤
c의 빠른 속도와 class의 개념을 결합시킨
언어를 만들기 위해서
c++을 개발했다고한다
c에서는 structure라는 개념이 있는데
이를 c++에서 object의 개념으로 확장시켰다고한다
Class
class란 무엇일까
아마 객체지향을 따로 배우지않은 사람도
한 번쯤은 들어봤을 것이다
나같은 경우는
첫 코딩을 iOS swift로 하는 바람에
코딩 시작부터 강제로(?)
배워버린게 객체지향과
class의 개념이었는데
class의 개념을 인터넷에 찾아보거나
gpt한테 물어보면
뭐 청사진, 붕어빵 틀,,
어쩌구 할텐데
조금 구체적으로 class의 정의에 대해서
정리해보려고한다
class는 내부적으로 2개가 있는데
Attribute와 Method가 있다
Attribute는 데이터에 관한 것을 말한다
데이터를 저장하고 관리하는 것을
class내에서 attribute라고 한다
Method는 procedural information을
다루는 것을 말하는데
간단하게 function이라고 이해하면 쉽다
data들이 어떻게 기능, 동작, 작동하는지를
정의한 것을 method라고 하고
method, function, behavior이라고도 불린다
이렇게 크게 두개의 정보가
class안에 들어있고
이러한 attribute와 method를
정의해놓은 일종의 template이
class이다
한마디로
int, string이 이런것들이
standard한 data type이라면
class는
내가 직접 만드는 data type이라고
생각하면 편하다
보통 class object라고 많이 부르는데
위에서 class는 template이라고 했는데
class object는 무엇일까?
그냥 class를 정의만 해준거라면
위에서 말했듯
단순히 blueprint처럼
설계도만 작성해준 것과 같다
이 blueprint를 실제 건물(object)라고
부를 수 있을 때는
실제 건물이 만들어졌을 때이다
따라서 class object라고
부를 수 있을 때는
실제 runtime에서
class가 메모리에 올라가서
한 개의 instance가 되었을 때
이를 class object라고 부른다
각각의 object들은
data attribute나 behaviors(methods)들을
entities(개체들)로 갖고 있다
이러한 object들은
instance라고도 부른다
class를 instance라고 부를 때는
class라는 하나의 설계도가
class를 생성하는 초안이 되었다는 것을
강조하고 싶을 때 이렇게 부른다
위의 why I created c++에서
C의 structure를 object로 만든게
c++이라고 했는데
그렇다면 c의 structure와
c++의 class를 비교해보자
위가 C에서의 structure이고
아래가 c++의 클래스이다
가장 큰 차이점은
structure에서는 method에 대한
정의는 내릴 수 없지만
class에서는 method에 대한
정의를 내릴 수 있다는 점이다
간단하게 SimpleVector라는
class를 c++에서 구현해보고자한다
우선 SimpleVector 안에는
3개의 attribute를 넣어줄거고
이는
int 포인터인 array
int인 size와 capacity이다
그 다음 3개의 method도
정의해줄건데
resize(), getSize(), addElement()
함수들을 정의해 줄 것이다
간단하게 위에서 정의한
attributes과 methods를 이용해서
SimpleVector라는 class를 정의해본 것이다
맨 처음
int* array, int size, int capacity를
정의해준다
그 다음 밑에
SimpleVector(int initialCapacity)
:size(0), capacity(initialCapacity)
와 같은 구문이 있는데
이는 constructor라고 한다
뒤에가서 더 자세히 배울 건데
이 constructor는
class가 instance화 될 때 초기화를 해주는 부분이고
자동으로 실행된다
constructor 내부에
array = new int[capacity];
와 같은 구문이 있는데
constructor가 호출되면서
capacity만큼 array가 할당된다
이건 나중에 constructor가 나올 때
더욱 자세히 알아보도록 하자
그 다음은
~SimpleVector()와 같은
구문이 있는데
이는 Destructor로
class object가 사라질 때 자동으로 실행되고
memory를 release시켜주는 역할을 한다
그 뒤에는
element를 추가해주는
addElement함수
capacity를 늘려서
새로운 array를 만들어주는
resize함수
size를 return하는
getSize함수가 정의되어있다
그리고 main함수에서
SimpleVector vec(10);
과 같은 방식으로
SimpleVector라는 class를
instance화해준다
class가 instance화 될 때를 잘 보자
SimpleVector vec(10);을 통해서
class를 instance화 해줬다
그럼 내부적으로는
vec이라는 class object에 들어간
attribute들이 메모리 공간에
연속적으로 할당된다
array는 포인터변수니깐
8바이트
size와 capacity int니깐
4바이트씩
총 16바이트가 stack에
vec을 위한 공간으로 할당된다
class 내부에 있는
attribute나 method에 접근하려면
어떻게 해야할까
같은 class 내부에서는
그냥 단순 이름만으로 호출이 가능하다
위를 보면 resize() 함수를 그냥
호출하고 있는 모습을 볼 수 있다
아니면 같은 class 내부에서
this -> capacity와 같이
this를 이용해서도 접근 가능하다
this는 파이썬의 self와 비슷한 역할로
class 자기 자신이 저장된 주소를 가리키는
class 포인터이다
하지만 외부에서 class 내부의
entities에 접근할 때는
dot operator를 이용해서 접근해야한다
vec.addElement와 같은 방식으로
접근할 수 있다
Constructor
아까 위에서 잠깐 언급했던
Constructor에 대해서
자세히 살펴보자
constructor는 class에 정의되어있는
특별한 메소드이다
class가 instance화가 될 때
자동으로 호출이 되어서
class를 initialize 해준다
python의 init()과
비슷한 역할이다
constructor의 이름은
무조건 class의 이름과 동일하며
아무것도 반환해선 안된다
그렇다면 아까 위에서 봤던
Constructor 옆에
: size(0), capacity(initialCapacity)
이 구문은 무슨 역할을 하는지 알아보자
이는
initializer list라고 불리는데
class에 있는 attribute들을
초기화 할 때 사용한다
constructor 내부에 넣어서
초기화 시켜주는게 아니냐는
생각이 분명 들텐데
constructor 내부에서 초기화를 해줘도되고
initalizer list와 같이 초기화를 해줘도되는데
두 가지 방법은 차이점이 있다
constructor 내부에서 초기화를 한다면
class가 declare가 되고 난 뒤
메모리에 class를 위한 공간이 생성이되고
그 후에 초기화가 수행이 되는 것이다
한 마디로, 기본 초기화 후에
추가적으로 수행이 된다는 느낌이다
하지만 initailizer list에 넣어서 초기화를 하면
class가 declare가 일어나는 동시에
초기화가 수행이 되는 것이다
capacity가 메모리에 할당이 되는 순간에
initialCapacity의 값을 넣어주는 것이다
선언과 동시에 실행이 된다
그렇다면 initilizer list가 왜 유용할까?
attribute를 class에 한 번 넣으면
const 형태로 바꾸지 못하게
선언하고싶을 때가 있다
위 ppt에서 코드를 보면
value라는 int 변수를
const로 정의했다
그런데 이런 value를
constructor 내부에서 초기화를 해주면
compile error가 발생한다
왜냐하면 const는 값을 바꿀 수가 없는데
초기화하면서 값을 바꿔주려하고있기 때문이다
이러한 경우에는 반드시
initializer list에서 초기화해줘야한다
또한 같은 예시에서
string str attribute에
초기화할 때
initStr를 받아서
constructor 내부에서
str를 initStr로 할당해주는데
이렇게 하면
내부적으로는 두 번에 걸쳐서
초기화를 해주는 것과 같다
맨처음에 빈 str가 할당되고
initStr를 받아와서 다시
str에 initStr가 할당되는 것이다
내부적으로 두번에 걸쳐 초기화해주는 것보단
initilizer list를 이용해서
str 선언과 동시에 initStr을
바로 할당해주는게 좋다
그렇다면 실제로
위 ppt의 코드처럼
vec = {1, 2, 3, 4, 5}와 같이
초기화해주려면
SimpleVector를 어떻게 구현해야할까?
SimpleVector의 constructor에
std::initializer_list를 인자로 받아야한다
이는 앞에서 나온 initializer_list와는 다른 개념이다
이렇게 해주면
size는 0으로 초기화가 되고
capacity는 elements의 size로 초기화가 된다
그다음 array에는 elements의
요소들이 들어가서 array가 초기화된다
object를 만들 때 컴파일러는
프로그래머가 어떤 argument를 넣었는지를
보고 맞는 constructor를 찾아서 호출한다
만약에 constructor가 없다면
empty argument를 갖는
constructor가 자동으로 생성돼서
호출되게 된다
이렇게 자동으로 생성되는
constructor를
default constructor라고 한다
하지만 최소 한 개의 constructor라도
있다면 default constructor는
생성이 되지 않는다
class constructor에 따라
어떻게 class를 초기화하는지
나열한 것이다
각 arguments들에 따라
그에 맞는 constructor를 찾아서
컴파일러가 자동으로 수행시킨다
Destructor
Destructor도 앞에서
잠깐 언급을 했는데
class 이름 앞에 ~를 붙여준다
Destructor가 하는 역할은
class가 할당을 했던 여러가지들을
다시 해제해주는 역할을 한다
여러 개의 destructor는
선언이 불가능하다
destructor 역시
class가 scope에서 사라질 때
자동으로 호출이 된다
class가 stack에 있다면
class object에 대한
모든 call이 끝났을 때
자동으로 호출이 되며
class가 heap에 있다면
개발자가 class에 할당된 메모리를
delete하는 순간 자동으로
destructor가 호출이 된다
하지만 manual하게도
호출이 가능하다고 한다
그렇다면 다른 method들은
어떻게 정의되었는지 확인해보자
resize는
기존의 capacity를 2배 늘린 다음
newArray를 만들어서
늘어난 capacity만큼 할당해준다
그 다음 newArray에
기존의 array element들을 넣어준다
기존 array는 delete해준 뒤
newArray는 resize함수가 끝나면
사라지는 local 변수이기 때문에
array에 newArray를 copy해준다
addElement는
size와 capacity가 같다면
새로운 element를 넣을 수 없으므로
만약 size가 같다면 resize를 시켜준 뒤
size의 위치에 새로운 element를 넣어주고
size를 한개 증가시켜준다
getSize는
size값을 반환해주는 함수이다
getSize() 뒤에 붙은 const는
해당 함수에서는 attribute를
변경하는 일이 없다고 컴파일러에게
알려주는 역할을 한다
그럼 compiler는
컴파일을 할 때
좀더 효율적으로 optimize를 할 수 있다
Access Specifiers
access specifier는
외부에서 class 내의
attribute나 method들에
접근 가능 여부를
설정해주는 것이다
c++에서는 크게
public, protected, private
3가지가 있다
public
memeber들이 프로그램의
어느 영역에 있어도 접근이 가능하다
main함수든 어디든
어디에서나 class 내부에 접근이 가능하다
protected
그냥 class 외부에서는
접근이 불가능하고
class 내부 혹은
상속받은 class나 friend class에서만
접근이 가능하다
private
가장 강한 보안이다
상속받은 class에서도
접근이 불가능하며
무조건 class 내부에서만 접근이 가능하다
안전하게 프로그래밍을 하기 위해선
수정할 일이 없다면
무조건 private으로 해주는게 좋다
그래야 abstraction으로
interface도 더욱 깔끔해지고
원하지않게 attribute가
수정되는 일이 없다
Class Template
그렇다면 지금까지 사용해왔던
SimpleVector class에
한 가지 불편한 점이 있다면 무엇일까
이는 바로 포인터 변수 array가
int로 데이터 타입이
고정되어있다는 점이다
int 대신에
double이나 string도
해주고싶다면 어떻게 해야할까
이전 시간에 공부했던
function template과 비슷하게
class template을 사용하면 된다
generic한 type T를
설정해준 뒤
int* array를
T* array로 해주면
int, string, double도
담을 수 있게 된다
class template로 정의된 class는
위와같이 선언해줄 수 있다
class 이름 옆에
data type을 정의해주면된다
그렇다면 class template을 이용해
class를 선언해주기 위해서
class 내부도 함께 바꿔보자
T* array로 변경해줬으니
이와 관련된 부분은 모두
T로 변경해줘야한다
newArray도 T로
addElement할 때
element도 T로 변경해준다
class template은 어떻게
instance화 되며
사용하면 어떤 장점이 있을까?
template의 arguement가 달라질 때마다
compiler는 그 argument에 특화되게
컴파일을 하고 실행을 한다
따라서 Optimize가 가능하고
implementation이 간단해지는
장점 등이 있다
class template을 사용한 예시는 뭐가 있을까
우리가 자주 사용하는
vector나 map 모두
class template으로 정의가 되어있다
이런걸 Standard Template Library라고해서
STL이라고 부르며
smart pointers도
이러한 방식으로 정의가 되어있다