constructor 작동 관련 기억 잘 안나면 ch10 ch11 ch12 계속 돌려보면 될듯
아래 member function들은 따로 define하지 않을 경우 자동으로 제공된다.
int a;
참고로 default constructor는 아무 constructor도 없어야 정의가 된다.
즉, copy constructor가 정의돼있더라도 default는 정의되지 않는다.
반대로 copy constructor는 따로 없으면 자동으로 정의된다.
Class_name(const Class_name &);
새로 만들어지는 object를 같은 type의 object 값으로 initialize할때 사용된다.
(이런 경우엔 object가 conversion function 거쳐서 constructor로 들어가는건가.. 짐작했었는데, 얘는 자동으로라도 무조건 생성되니까 무조건 copy constructor 거쳐서 가겠네)
(copy만할거니까 거의 다 const로 받네)
> Deep Copy
class가 new로 할당된 곳을 가리키는 pointer를 멤버로 가진다면, copy constructor를 정의해야한다.
기본 copy constructor에 의존할 경우 두 object의 멤버가 같은 memory를 가리킬 수 있고,
destructor가 호출돼 그 메모리를 해제한다면 이미 해제된 메모리를 해제하게되어 UB가 된다.
따라서 새로 new로 할당받아서 복사하는 작업 필요
새로운 object가 만들어질때 값이 변하는 static member가 있다면,
이때도 copy constructor를 따로 정의해 줘야한다.
> 사용법
1. StringBad ditto(motto);
2. StringBad * pStringBad = new StringBad(motto);
3. StringBad metoo = motto;
4. StringBad also = StringBad(motto);
이렇게 같은 type의 object를 이용해 initialize할때 copy constructor가 호출된다.
2번은 Ch9랑 Ch10에서 모두 봤던 문법이다. new를 이용해 memory 할당 받으며 초기화.
3번과 4번은 Ch10에서 봤듯이 (1)1번처럼 바뀌어서 바로 copy constructor이용해 초기화 될 수도 있고,
(2)copy constructor로 temporary를 만들어서 이를 assign할 수도 있다.
Q. 3번 문법은 초기화라서 constructor를 쓰긴해야될건데,
temporary 만들어서 assgin할거면 그냥 바로 motto에 assignment operator 적용하면 되는 것 아닌가?
A. initializing 문법이기때문에 (copy) constructor를 사용해야 하는 것이다.
초기화때 사용하라고 constructor를 만드는건데, 저 초기화에는 그게 적용이 안되버리면 또 허점생긴다.
그래서 좀 돌아가더라도 copy constructor로 temporary object를 만들고, 그걸 assign한다.
이외에도 함수에 object를 pass by value로 넘기거나, 함수가 object를 return할때도 사용된다.
전자의 경우 "parameter가 initialize"되며 object가 들어오면 당연히 copy constructor가 사용되는 것이고,
후자의 경우 return 값 임시 저장 위한 object가 만들어져야하니 마찬가지인 것이다.
object는 built-in type이랑 다르게 object는 parameter로 넘겨줄때도 함수가 호출된다는 소리.
passing by reference가 좋은 이유.
Class_name & Class_name::operator=(const Class_name &);
member 함수로만 overload되는 operator
copy constructor와 마찬가지로 deep copy 를 해줘야 할 때가 있다.
> 만들어보자
copy constructor와 비슷한데 차이가 있다.
assign되어지는 object에 이미 할당된 data는 delete
를 이용해 삭제해 줘야한다.
: a=b;
에서 a의 값들을 완전히 지우지 않고 b의 값을 덮어쓰면, a의 지워지지 않은 값들이 메모리를 차지해 낭비
스스로 assign하는 것에 대한 protect 기능이 있어야 한다.
: 아니면 1번에 의해 본인 data가 지워질 수 있다.
overloading 함수는 invoking object의 reference를 반환하게 한다.
: a=b=c;
같이 연속적인 assign 기능을 구현할 수 있다. assign이 수정되는 기능이라 const는 빼야함.
tip. operator overload할때 return 값을 어떻게 할지 헷갈리면,
C/C++에서 built-in type 연산할때 expression value가 뭐가 되는지 고려해보면 좋다.
실제로 기본 type에 assignment 연산적용하면 해당 expression은 lvalue의 값을 가진다.
StringBad & StringBad::operator=(const StringBad & st) {
if (this == &st) //스스로 assign하는가
return *this;
delete [] str; //기존 data 제거
len = st.len;
str = new char [len + 1];
std::strcpy(str, st.str);
return *this;
}
> 고찰
Banana A = Banana(3);
이나 A = 3;
같은 것들을 다시 자세히 보자.
알맞은 constructor로 임시 object가 만들어지고(형변환) assignment operator를 이용해 assign(copy)이 된다.
(전자는 바로 ctor이 호출 될 수도 있지만, 여기선 임시 개체 만들고 assign되는 경우를 말하는 것)
assign시에는 이런 형변환을 사용하고 싶지 않다면 곧바로 assign 될 수 있도록, assignment operator를 해당 type에 대해 추가정의하면 된다.
r-value reference가 등장한 뒤론, 임시 object가 만들어지지도 않는다.
자세한건 ch 18 참고
new
사용StringBad::StringBad(const char * s) {
len = std::strlen(s);
str = new char[len+1];
std::strcpy(str, s);
}
/*
그냥 `str = s;` 로 주소만 복사해도 상관없지않나? 왜 굳이 전체 내용을 복사하려하나?
-> string literal이 넘어오면 수정이 불가능하다.
그리고 애초에 당연히 원본은 안건드리고 copy하는게 맞지.. 공유하면 골치아파짐
*/
(중요) 이렇게 Constructor에서 new를 사용해 공간을 할당받았다면 다음 사항들을 이행
new
에 대응되는 delete
를 무조건 destructor에 위치시킨다.
둘은 compatible 해야한다. ex) str = new char[10];
라면 delete [] str;
(delete하지 않고 그냥 object없어져버리면, 해당 공간은 아무도 가리키지않고 메모리 낭비됨)
Constructor가 여러개라면 전부 같은 방식으로 할당받아야 한다. destructor는 하나 뿐이기 때문이다.
null pointer를 사용하는 것은 허용(얘는 어떤 delete와도 호환) (호환관련해선 '자잘한것들' 참고)
C에선 잘 알아보려고 NULL macro를 주로 사용했지만, C++에선 그냥 `0`을 많이 사용한다.
물론 C++11 이후론 nullptr 추천..
deep copy를 하는 copy constructor를 정의해야 한다.
(object 생성과 연관되는 static member있으면 update도 해야됨)
deep copy를 하는 assignment operator를 정의해야 한다.
class Magazine { //얘는 new를 쓰진 않는데 String 멤버에 new가 관여
private:
String title;
...
};
이런 경우는 또 Magazine
의 copy constructor와 assignment operator를 만들어야하나?
ㄴㄴ. Magazine
object끼리 copy/assign이 일어나면 멤버들끼리 copy/assign을 할때 알아서 String
class의 copy constructor와 assignment operator를 사용한다.
Magazine이 copy constructor/assignment operator를 만들어야되는 상황이면
String의 copy constructor/assignment operator를 직접 호출해줘야된다고하네
자세한건 Ch13..
new
활용하는지 잘 보기뒷부분에서 queue를 구현하는데 linked list를 활용한다.
각 node는 new
로 할당받는데, 어차피 dequeue
함수가 delete
하긴하지만,
Queue object가 해제될때 전부 지워지는지 장담은 못한다.
이런 경우에도 destructor로 확실히 지워야한다.
const
referenceconst
reference 일때operator=()
나 operator<<()
overload할때const
object1. paramter를 const
reference로 받아서 반환해줄거라면 return도 그럴 수 밖에 없다.(non const에 const를 주면 안되니까)
(아니면 const
reference 받아서 3번처럼 일반 object copy되게 반환해도 되긴함)
2.
1) operator=()
를 작성할때나
2) operator<<()
를 작성할때
둘 다 효율성과 chained assignment(output)를 위해 사용된다. (1의 경우는 그냥 object 반환해도 연쇄 assign이 거의 되긴 하지만 (a=b)=c;
처럼 좀만 꼬으면 안됨.)
1의 경우는 assignment이므로 당연히 값이 변경될 수 있어서 당연히 const
는 안되고((a=b)=c;
생각해보면됨),
2의 경우는 왠진 모르겠는데 const로하니 안되네..(내부적으로 cout을 건드리나?)
2의 경우 ostream
class는 public copy constructor도 없기때문에 object로의 반환은 애초에 안된다.
주로 효율성때문에 1번과 2번 방법을 사용
1번과 2번은 reference를 반환하므로 object의 lifetime을 잘 확인해야한다.(반환하고 없어져버리면 안됨.)
3/4. local object를 반환해야한다면, 3번/4번밖에 방법이 없다.
일반적인 산술연산이 보통 그런데, b + c;
를 했을때 b
나 c
의 값은 바뀌면 안된다.
이럴땐 지역 변수 object 만들어서 반환한다.
그런데 3번으로 하면 b + c = a;
같은 문법이 가능해져버린다.
이 문법은 4번처럼 const
를 반환함으로써 막을 수 있다.
함수 return 값은 reference가 아니라면 모두 rvalue로 취급된다.
그런데 어떻게 `b+c=a;`가 가능할까, rvalue인데 말이 안되지않나?
: rvalue일때 assign이 성립하지 않는다는건 기본 built-in type에서나 통하는 얘기다.
`=`는 object에겐 `+`같은 연산자이다. `=` 연산이 정의돼있다면(보통 기본적으로 정의됨), 위 연산이 된다.
당연히 기본 int같은 type은 const 안붙이고 그냥 반환해도 `b+c=a;` 같은 꼴이 나올 수 없다.
new
> 문제점
1. 주소를 넘겨준대로 사용하기때문에 기존 data를 덮어 쓸 수도 있다.
: 아래의 경우 pc3
가 pc1
의 내용을 덮어쓴다.
pc1 = new (buffer) JustTesting;
pc3 = new (buffer) JustTesting("Better Idea", 6);
2. destructor가 호출되지 않는다.
: pc1 = new (buffer) JustTesting;
이렇게 할당 받았다고 해보자.
delete
는 placement new가 아닌 그냥 new
로 할당된 공간에만 적용이 된다.
그렇기때문에 delete pc1;
이것 자체가 안되기때문에
placement new로 할당받은 공간은 따로 해제할 수 없다.
그러면 buffer
자체가 해제되면 그 안의 object들에 대해 destructor 호출되나?
delete [] buffer;
를 하든, buffer
가 stack 영역에 있어서 block 끝날때 자동해제되든,
buffer가 해제될때도 그 공간안에 할당된 object들에 대해 destructor는 따로 호출되지 않는다.
여기 내용이랑은 큰 연관은 없는데,,
buffer가 heap에서 할당받아온 공간이라면, `delete [] pc1;`은 작동한다.
결국 delete는 주소를 보고 판단하는데, pc1과 buffer의 시작주소가 같아서 `[]`만 넣어준다면 작동함.
단, 원래 말했듯 `delete pc1;`은 어림도 없다.
> 해결 방법
1. 다음 시작 주소를 내가 알아서 control해야한다.
pc1 = new (buffer) JustTesting;`이라고 했으면,
pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6);
2. 적당한 위치에 직접 destructor를 호출한다.
참고로 placement new로 할당받은 역순으로 호출해야한다.
(나중에 만들어진 것이 이전에 만들어진 것에 dependencies가 있을 수 있기 때문이다.)
얘네가 할당 받아온 공간(buffer)은 당연히 destructor들 호출한 이후에 해제되도록 해야한다.
pc3->~JustTesting(); //pc3은 object에 대한 pointer이니 멤버함수 접근하려면 `->` 써야함
pc1->~JustTesting();
delete [] buffer;
placement new 언제 쓰면 좋지?
: 최적화시킬때 섞어주면 좋겠네, 매번 할당해제 하는 것 보다 할당받은 메모리 위에서 갖고 노는게 확실히 빠름.
(k&r chapter 8 보면 알겠지만, 사실 new(malloc)도 미리 system call통해 heap에서 크게 할당받아온 메모리를 떼주는거임. new와 placement new는 사실 어느 공간이냐의 차이지, 이런 면에선 같다.)
> Queue 만들어보자
queue는 array보단 linked list
rear에 삽입해야되니, 기존 linked list와는 달리 끝을 가리키는 포인터도 있으면 좋다.
class declaration 내에 structure나 class declaration을 또 위치시킬 수 있다.
private에 위치한다면 당연히 class 내에서밖에 못쓰고,
public에 위치하면다면 밖에서도 Queue::Node
식으로 scope operator를 이용해 해당 type을 사용할 수 있다.
개념적으로, constructor를 호출하게되면 일단 object를 만들고(공간을 할당하고) {}
안으로 들어가서 constructor의 남은 code를 실행한다.
Queue::Queue(int qs) { qsize = qs; //qsize는 non-static const member }
그러므로 위 code는 const에 assign을 하는 꼴이므로 안된다. const엔 initialize 밖에 못한다.
> Member initializer list
이때 "member initializer list"을 사용한다. (prototype X, definition에만 사용)
parentheses 뒤, brakets 앞에 위치한다. comma로 구분해서 여러개 쓸 수 있다.
Queue::Queue(int qs) : qsize(qs), front(NULL) { ... }
> Member In-Class Initialization (C++11)
C++11부턴 class declaration 내에서 초기화(?)를 할 수 있다.
class Classy {
int mem1 = 10;
int mem2 = 20;
};
위 표현은 사실상 아래와 같이 constructor에 member initializer list를 추가하는 효과를 가진다. 그래서 non-static const는 위처럼 in-class 초기화 문법을 사용해도 OK
(레퍼런스도 이렇게 하려면 하는데 변수가 없으니...)
Classy::Classy() : mem1(10), mem2(20) { ... }
이렇게는 기본으로 있는거고, constructor를 좀 다르게 정의한다거나하면 그 위에 덮어씌워진다.
ex) Classy::Classy() : mem1(n) { ... }
으로 해버리면, mem1은 n이되고 mem2는 20이 된다.
> 추가
constructor의 parameter가 class type일때 이 문법을 사용하면 더 빠르다.
(더 정확히하자면, member중에 class type이 있어서 constructor가 해당 object를 인자로 받을때)
(1) TTP::TTP (const string & fn) { firstname = fn; }
보다는
(2) TTP::TTP (const string & fn) : firstname(fn) { }
이게 더 빠르단 소리
(firstname은 string
type)
일단 먼저 (1)의 경우, fn
을 initialize하기위해 일단 copy constructor가 실행되고,
firstname
는 string
type이므로 string
class의 default constructor가 호출돼 따로 만들어진다.
그리고 assignment operator를 이용해 assign이 되는 것이다.
다음으로 (2)의 경우, 일단 마찬가지로 parameter 만들기위해 copy constructor는 호출되고,
firstname
에 곧바로 copy constructor를 적용해 firstname
object를 초기화하며 만들어낸다.
parameter 초기화 단계를 빼고보면,
(1)은 default constructor+assign인데, (2)는 바로 copy constructor임.
private
에 당장 필요없는 기능 넣기자동으로 생성되는 special member function이 deep copy를 하지 않기때문에,
object에 new
로 할당받은 공간이 있다면 우리가 직접 해줘야할 일들이 있다.
그런데 당장은 copy나 assign 기능을 사용하지 않아서 deep copy를 구현하지 않아도 문제가 안된다면?
1. 그래도 미래엔 쓸 수 있으니 deep copy를 구현을 해두거나
2. 크게 구현하진 않고 그냥 private
에 넣음으로써 나중에 구현안한걸 깜빡하고 사용했을때 쉽게 알 수 있도록 한다.
special member 함수들은 자동생성되기때문에 deep copy가 필요하다면 구현을 하거나,
구현하지 않을거라면 아래처럼 private에 넣어서 실수로 사용했을때 구현 안됐음을 알린다.
class Queue {
private:
Queue(const Queue & q) {}
Queue & operator=(const Queue & q) { return *this; }
...
}
문제점
copy constructor는 함수 인자 넘겨받을때다 return할때도 쓰이기때문에 조심해야한다.
그래서 private에 구현해뒀다면,
인자는 reference로 받아야지만 작동하고, temporary object가 필요한 상황은 불가능해져버린다.
`+` operator를 overload해야한다거나... 이런게 불가능
delete []
는 아래 둘과 compatible하다.
new []
에 의해 초기화된 포인터나delete
와도 호환됨)0
으로 표현하거나 안헷갈리려고 (void*)0
으로 하거나 NULL
macro를 썼다.nullptr
keyword 사용.그래서 String
class의 default constructor를 정의할때
char * str = new char[1];
이렇게 할당 받는다. new char;
은 delete [] str;
과 호환 안됨.
혹은 null pointer여도 호환되니까 char * str = 0;
으로 초기화 하기도 한다.
[]
연산자를 overloading -> city[0];
라고 한다면 city
는 첫번째 operand, 0
은 두번째 operand
char & String::operator[](int i) {
return str[i];
}
이렇게하면 private 값들 막 바꿀 수도 있는거 아닌가?
: ㅇㅇ 맞다. private은 직접적인 접근만 막는거지 이렇게 public 함수통한 우회는 상관없다.
public만 잘 관리하면 되는거지.
const member function은 일반 member function과 signature가 다르므로 overloading이 가능하다.
: member 함수엔 *this
argument도 사실상 추가되는데, const 멤버함수의 경우엔 const *this
로 받는 셈이다.
(Ch8)overloading시 signature이 reference이거나 pointer일 경우 const 여부를 구분하므로, 둘은 구분된다.
예시)
[]
연산도 object가 const일 경우 위 함수만으론 진행이 안된다. Ch10에서 봤듯이 멤버함수가 object를 변경하지 않는다는걸 알려줘야한다.
(*this
매개변수 기준으로 보자면 non const 매개변수에 const를 넣는 꼴이니 당연 안되지)
char & String::operator[](int i) const {
return str[i];
}
그래서 이렇게 const Member function을 추가로 overload해준다.
operator overloading시 자동 형변환이 얼마나 일어나는지 보자.
자주 발생한다면, 그에 맞는 특수화된 operator 함수를 제공해주는 것이 좋다.
ex)
name = temp;
(name은 String object, temp는 char [40])
1) temp
는 constructor에 의해 temporary object가 만들어진다.(자동 형변환)
2) 그리고 assignment operator(=
)가 값들을 name에 copy
3) destructor 호출해서 temporary object 제거
이렇게 간단한 operator에서도 자동형변환이 발생하면 process가 간단하지만은 않다.
이런 경우 =
operator가 char *
을 따로 처리하도록 overload하면 더 효율적으로 해결된다.
String & String::operator=(const char * s) {
delete [] str;
len = std::strlen(s);
str = new char[len+1];
std::strcpy(str, s);
return *this;
}
> in-class initialization
class라는 것은 object를 만들때 그 object가 어떻게 메모리를 할당받을지 알려주는 틀에 불과하다.
따라서 class를 만들때는 아무런 메모리 할당이 일어나지 않는다.
그렇기때문에 class내에서 member를 initialize하는건 어불성설이다.
(C++11 이후로 그런 형태는 가능한데, 그때도 할당은 안일어난다.)
앞서 배웠듯 static const int인 경우에만 in-class initialization이 가능하다. 이유
> static
member
class 내부에서 static으로 선언된 변수는 다른 member 변수처럼 object마다 하나씩 존재하는게 아니라,
모든 object가 그 하나의 변수를 공유한다.
여기 답변 code보면 알겠지만, sizeof연산을 해도 애초에 같은 entity로 고려하지 않는 것 같다.
이를 초기화 하려면, 바로 위에서 봤듯이 inclass 초기화는 안되고,
methods file에서(not class declaration file) int StringBad::num_strings = 0;
식으로 scope operator(::
)를 사용해 초기화 한다.
class declaration file은 주로 header file인데, 거기서 초기화 해버리면 여러 file에서 include시 문제가 된다.
한 변수에 대한 initialization이 여러개 존재해버리면 당연히 error이다.
또 이 친구는 모든 object가 공유하는 member라서 굳이 object를 통하지않고도 `::`를 통해서 구별할 수 있다.
그리고 위에서 말했듯이 얘도 static이라고 해서 메모리가 할당됐다는건 아니다.
그렇기때문에 정의는 따로 해줘야되는데 그래서 int까지 앞에 붙여주는 것이다.
(이미 할당됐으니 StringBad::num_strings = 0;으로 해도되지않나.. 라고 생각했었음.)
궁금증1. private안에있어도 저렇게 초기화가 되네?
찾아봐도 명확한 답변이 없는거 같음. 그냥 private static이면 초기화 할땐 접근이 가능하다. 라고 알고있는게 맞는듯.
참고: https://stackoverflow.com/questions/24707303/private-static-class-members
궁금증2. 왜 global scope에서만 static 멤버 변수 초기화 가능하지?
C++03 9.4.2.2에 보면 class 선언을 포함하는 namespace scope에서 초기화 가능하다고 돼있음.
그러니까 local은 안되지.
Static Member Functions
member 함수가 static
으로 선언되면,
1. 얘도 특정 object에 묶이는 것이 아니기때문에, object를 통해서도 호출 되지만, public에 선언된다면 Class_name::Function_name()
식으로도 호출이 가능하다.
2. 말했듯 특정 object와 연관되지 않기때문에(this도 안받음), static data member에만 접근할 수 있다.
(물론 매개변수로 object 받아서 .
으로 접근해서 사용하는건 상관없음. 기본설정을 말하는거임. 멤버함수이므로 private 접근 권한은 있다.)
선언 방식은 class내에서 static
사용하고 class밖의 정의엔 static
사용하지 않는다.
멤버함수이므로 정의할땐 당연히 Class_name::
가 앞에 붙어야한다.
>고찰
얘 기능들이 friend로 솔직히 다 커버 가능해보이는데 어떤 쓸모가 있는지 모르겠다.
다른 점이라하면, static member 함수는 멤버함수이기때문에 class scope에 포함돼서 ::
operator 사용 안하고도 static 멤버 변수 접근할 수 있다는 정도..
그외에도 뭐 차이점있긴한데...
그냥 딱 사용처가 static member 변수 건드리는 정도로 제한되는 듯 하다.
거기에 특수화(?)돼서 사용된다고 일단 그정도로만 알고있자.
※static member function 언제 쓰나요?※
공부하다가 알게 된건데, static member function이란 개념을 다른 언어에서도 많이 지원함.
그냥 그 type(class)에서 지원하는 어떤 기능을 static member function으로 하면 유용한듯.
예를들어 account class가 있을때 전체 account 개수를 알 수 있게 해준다거나..
그리고 그렇게 static member function이 접근하는 특성은 거의 static data member겠지, 그러니까 static data mamber에만 접근 가능하단거고..
friend function이랑은 좀 느낌이 다르지, frined는 그 class에 속하지도 않잖아.
class member 중에 char pointer가 있으면 당연히 공간할당받아서 strcpy 해야겠지만,
char array가 있는 경우도 비슷하다.
공간만 할당받아져있으니 strcpy 써서 복사하거나, member initialization list 사용해야한다.
(constructor 내에서의 초기화는 initialization이 아니라 object가 먼저 할당되고 assign하는 꼴)
assignment operator에서 본인을 assign하는지 확인할때는 주소로 확인하자.
object 자체로 비교하면 const면 매칭이 안되기도하고 좀 복잡. 주소보다 비효율적이기도한듯.
그리고 무엇보다 서로 다른 object인데 값만 같은 경우도 있을 수 있다.
ornamental 장식용의
supersede 대체하다
FYI(ForYourInformation) 참고로
draft 원고, 초안
peculiar 이상한, 기이한
demise 종말
cure 치료하다
emulate 모방하다
excerpt 발췌
quandary 진퇴양난
third of A A의 삼분의 일