※ 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다.


즉, 34~35행은 AAA mem = ref; 로 해석이 된다.

그러므로 복사 생성자만 호출이 된다.


하지만 45~48행은 생성자와 대입연산자 이 두개가 호출이 된다. 

즉, AAA mem;

     mem = ref; 이렇게 해석이되므로 인해.. 생성자 호출이 되어지고 그다음 대입연산자가 호출이 된다. 


※ 생성자의 몸체부분에서 대입연산을 통한 초기화를 진행하면, 선언과 초기화를 각각 별도의 문장에서 진행하는 형태로 바이너리 코드가 생성된다.



위의 예제에서 보이듯이 이니셜라이저를 이용해서 초기화를 진행하면, 함수의 호출횟수를 줄일 수 있어서, 그리고 초기화의 과정을 단순화시킬 수 있어서 약간의 성능향상을 기대할 수 있다.


Posted by scii
:

※ 유도 클래스의 생성자에는 아무런 명시를 하지 않아도 기초 클래스의 생성자가 호출되지만, 

유도 클래스의 대입 연산자에는 아무런 명시를 하지 않으면, 기초 클래스의 대입 연산자가 호출되지 않는다.



# 36행 ~ 43행: 이 대입 연산자를 정의한 것을 지워버리면 디폴트 대입 연산자가 자동으로 생성된다. 그리고는 기초클래스의 대입연산자가를 자동적으로 호출한다.

하지만, 대입 연산자를 정의할 생각이 있다면 이렇듯 기초클래스의 대입연산자를 명시적으로 호출해야 한다.


# 13행: C++ 에서는 A형 참조자는 A 객체 또는 A를 직접 혹은 간접적으로 상속하는 모든 객체를 참조할 수 있기 때문에 이렇게 가능하다.


#include <iostream>

#include <cstring>

using namespace std;


class Book

{

private:

char* title;

char* isbn;

int price;


public:

Book(char* title, char* isbn, int value)

:price(value)

{

this->title = new char[strlen(title)+1];

this->isbn = new char[strlen(isbn)+1];

strcpy(this->title, title);

strcpy(this->isbn, isbn);

}


explicit Book(const Book& ref) //복사 생성자

:price(ref.price)

{

title = new char[strlen(ref.title)+1];

isbn = new char[strlen(ref.isbn)+1];

strcpy(title, ref.title);

strcpy(isbn, ref.isbn);

}


Book& operator=(const Book& ref) //대입 연산자

{

delete []title;

delete []isbn;


price = ref.price;

title = new char[strlen(ref.title)+1];

isbn = new char[strlen(ref.isbn)+1];

strcpy(title, ref.title);

strcpy(isbn, ref.isbn);

return *this;

}


void ShowBookInfo() const

{

cout<<"제목: "<<title<<endl;

cout<<"ISBN: "<<isbn<<endl;

cout<<"가격: "<<price<<endl;

}


~Book()

{

delete []title;

delete []isbn;

}

};


class EBook :public Book

{

private:

char* DRMKey;


public:

EBook(char* title, char* isbn, int value, char* key)

:Book(title, isbn, value)

{

DRMKey = new char[strlen(key)+1];

strcpy(DRMKey, key);

}


EBook(const EBook& ref) //복사 생성자

:Book(ref) //기초클래스의 복사 생성자 호출

{

DRMKey = new char[strlen(ref.DRMKey)+1];

strcpy(DRMKey, ref.DRMKey);

}


EBook& operator=(const EBook& ref) //대입 연산자

{

delete []DRMKey;


Book::operator=(ref); //기초클래스의 대입 연산자 호출.

DRMKey = new char[strlen(ref.DRMKey)+1];

strcpy(DRMKey, ref.DRMKey);

return *this;

}


void ShowEBookInfo()

{

ShowBookInfo();

cout<<"인증키: "<<DRMKey<<endl;

}


~EBook()

{

delete []DRMKey;

}

};


int main(void)

{

Book book("zzzz", "adf2342343", 23434);

book.ShowBookInfo();

cout<<endl;


EBook ebook("asdf", "s234234", 23423, "1234ee");

ebook.ShowEBookInfo();

cout<<endl<<endl;


///////////////////////////////////////////////////////////////////


Book book1(book); //복사생성자 호출

book1.ShowBookInfo();

cout<<endl;


Book book2("thg", "234", 234221);

book1 = book2; //대입 연산자 호출

book1.ShowBookInfo();

cout<<endl;

///////////////////////////////////////////////////////////////////


EBook ebook1("asdf", "wer", 123, "qwe");

ebook1 = ebook; //대입연산자 호출 ebook1.operator=(ebook);

ebook1.ShowEBookInfo();

cout<<endl;


EBook ebook2(ebook1); //복사생성자호출 ebook2 = ebook1;

ebook2.ShowEBookInfo();


return 0;

}


Posted by scii
:

int main(void)

{

Point pos1(5, 5);        //복사생성자의 대표적인 상황.

Point pos2 = pos1;

}


※ 중요한 사실은 새로 생성하는 객체 pos2의 초기회에 기존에 생성된 객체 pos1이 사용되었다는 점이다.


int main(void)

{

Point pos1(5, 5);        //대입 연산자의 대표적인 상황.

Point pos2(3, 3);


pos2 = pos1;

}


※ 중요한 사실은 pos2도, 그리고 pos1도 이미 생성 및 초기화가 진행된 객체라는 사실이다.





디폴트 대입 연산자의 문제..



called destructor! 이것이 딱 한번만 출력되었다는 것을 보아 소멸자의 호출과정에서 문제가 발생했음을 알 수 있다.


37행: man2가 가리키고 있던 주소 값을 읽게 되므로, 더 이상 문자열에 접근이 불가능하다.

 때문에 소멸도 불가능한 상태가 되어 메모리의 누수로 이어지게 된다.



# 정리하면, 생성자 내에서 동적 할당을 하는 경우, 디폴트 대입 연산자는 두 가지 문제를 일으키므로 다음의 형태로 직접 대입 연산자를 정의해야 한다.


1. 깊은 복사를 진행하도록 정의한다. 

2. 메모리 누수가 발생하지 않도록, 깊은 복사에 앞서 메모리 해제의 과정을 거친다.




Posted by scii
:

대입 연산자의 오버로딩은 클래스 정의에 있어서 생성자, 복사 생성자와 함께 빠질 수 없는 요소이다.


#include <iostream>

using namespace std;


class First

{

private:

int num1, num2;

public:

First(int n1=0, int n2=0)

:num1(n1), num2(n2)

{ }


void ShowData()

{

cout<<num1<<", "<<num2<<endl;

}

};


class Second

{

private:

int num3, num4;

public:

Second(int n3=0, int n4=0)

:num3(n3), num4(n4)

{ }


void ShowData()

{

cout<<num3<<", "<<num4<<endl;

}


Second& operator=(const Second& ref)        //반환형이 참조형임을 주목!!

{

cout<<"Second& operator=()"<<endl;

num3 = ref.num3;

num4 = ref.num4;

return *this;

}

};


int main(void)

{

First fsrc(111, 222);

First fcpy;


Second ssrc(333, 444);

Second scpy;


fcpy = fsrc; //fcpy.operator=(fsrc);

scpy = ssrc;


fcpy.ShowData();

scpy.ShowData();


///////////////////////

First fob1, fob2;

Second sob1, sob2;


fob1 = fob2 = fsrc;

sob1 = sob2 = ssrc;        //반환형이 참조형이므로 이런식의 연산이 가능하다.


fob1.ShowData();

fob2.ShowData();

sob1.ShowData();

sob2.ShowData();


return 0;

}



First 클래스에 자동으로 삽입된 디폴트 대입 연산자는 다음과 같다.


First& operator=(const First& ref)

{

num1=ref.num1;

num2=ref.num2;

return *this;

}


※ 멤버 대 멤버의 복사가 이뤄지는 것을 보면서, C언어의 구조체 변수간 대입연산의 결과와 비슷하다고 생각하기 쉽다. 

그러나 앞서 보였듯이, 객체간의 대입연산은 C언어의 구조체 변수간의 대입연산과 본질적으로 다르다.

이는 단순한 대입연산이 아닌, 대입 연산자를 오버로딩 한 함수의 호출이기 때문이다.

Posted by scii
:

Consol out, Consol In, End line 



cout과 endl 직접 구현하는 예제.


이 예제는 cout<<"wer"<<12123<<endl; 이런 식으로 할 수가 없다 .그래서 이런 연산을 가능케 하기위해 바꿔야 한다.





cout은 ostream 클래스의 객체이다.


ostream은 이름공간 std 안에 선언되어 있다.




만약....

int main(void)

{

Point pos(3, 5);

cout<<pos<<endl;    //3, 5 출력.

...

}


이런 유형의 연산이 가능하게 하고자 한다.

이것이 가능하기 위해선 << 연산자가 오버로딩 되어 있어야 한다. 그리고 만약 멤버함수의 형태로 오버로딩을 한다면, 다음과 같이 해석이 가능해야 하고,

cout.operator<<(pos);


전역함수의 형태로 오버로딩을 한다면, 다음과 같이 해석이 가능해야 한다.

operator<<(cout, pos);


어떠한 방법을 선택해야 하는가...?

# 멤버함수에 의한 방법을 선택하면 cout 객체의 멤버함수를 하나 추가해야 하므로, ostream 클래스를 정정해야 한다. 그런데 이는 불가능한 방법이다.


# 전역함수에 의한 방법을 택하는 수밖에 없다. 때문에 전역함수로 정의해야 한다.

&ostream operator<<(ostream &os, const Point &pos) { ... }




'Programming > C++' 카테고리의 다른 글

디폴트 대입 연산자의 문제점  (0) 2012.09.11
대입 연산자의 오버로딩2  (0) 2012.09.10
교환법칙 문제의 해결  (0) 2012.08.28
전위증가와 후위증가의 구분  (0) 2012.08.27
단항 연산자의 오버로딩  (0) 2012.08.27
Posted by scii
:

교환법칙이란?


=> A + B 의 결과는 B + A 의 결과와 같음을 뜻한다. 즉, 연산자를 중시능로 한 피연산자의 위치는 연산의 결과에 아무런 영향을 미치지 않는다는 법칙이다.

대표적으로 교환법칙이 성립하는 연산으로는 곱셈연산과 덧셈연산이 있다.



연산자 오버로딩을 통해, 서로 다른 자료형의 두 데이터간의 연산을 가능하게하는 예제)



pos*3; 은 pos.operator*(3); 으로 해석이 된다.


위 예제에서 오버로딩 한 곱셈 연산자의 경우, Point 클래스의 멤버함수 형태로 정의했기 때문에 Point 객체가 * 연산자의 왼편에 와야 한다. 


교환법칙이 성립되려면 cpy = 3 * pos; 가 성립되어야 하는데 멤버함수로는 불가능하다.

하지만 전역함수로 하면 불가능이 가능해진다.




교환법칙의 성립을 위한 구현


교환법칙이 성립하도록하려면

cpy = 3 * pos;


전역함수의 형태로 곱셈 연산자를 오버로딩 하는 수 밖에 없다. 즉, 위의 문장이 다음과 같이 해석이 되도록 연산자를 오버로딩 해야 한다. 

cpy = operator*(3, pos);


이를 위해서는 operator* 함수를 다음과 같이 정의해야 한다. 


Point operator*(int times, Point& ref)

{

Point pos(ref.xpos*times, ref.ypos*times);

return pos;

}


그런데, 다음과 같이 3*pos 를 pos*3 이 되도록 바꾸는 형태로 오버로딩을 해도 된다. 


Point operator*(int times, Point& ref)

{

return ref*times;

}



교환법칙이 성립되면, 이렇듯 보다 자연스러운 연산문의 구성이 가능하다.

Posted by scii
:

++ 연사자와 -- 연산자는 피연산자의 위치에 따라서 의미가 달라진다.


# 연산자 오버로딩에서 ++ 연산자와 -- 연산자는 전위연산에 해당할까 후위연산에 해당할까?

=> 오버로딩 한 연산자를 호출하는 형태만 봐도, 전위증가, 전위감소 연산에 해당한다는 사실을 알 수 있다.


그렇다면, 후위증가, 후위감소 연산에 대한 연산자 오버로딩은 어떻게 해야 할까?

=> C++ 에서는 전위 및 후위 연산에 대한 해석방식에 대해 다음의 규칙을 정해놓고 있다. 


++pos                    ->                    pos.operator++();

pos++                    ->                    pos.operator++(int);


--pos                    ->                    pos.operator--();

pos--                    ->                    pos.operator--(int);


즉, 키워드 int 를 이용해서 후위연산에 대한 함수를 전위연산에 대한 함수와 구분하고 있다. 물론 여기서 사용된 int는 단지 후위연산을 구분하기 위한 목적으로 선택된 것일 뿐!! 아무 상관 없다.



#include <iostream>

using namespace std;


class Point

{

private:

int xpos, ypos;


public:

Point(int x = 0, int y = 0)

:xpos(x), ypos(y)

{ }


void Show() const

{

cout<<xpos<<", "<<ypos<<endl;

}


Point& operator++() //선위증가

{

xpos+=1;

ypos+=1;


return *this;

}


const Point operator++(int) //후위증가

{

const Point retObj(*this); //디폴트 복사생성자로 인해 멤버 대 멤버로 복사진행.

xpos+=1;

ypos+=1;


return retObj;

}


friend Point& operator--(Point &);

friend const Point operator--(Point &, int);

};


Point& operator--(Point &ref) //선위감소

{

ref.xpos-=1;

ref.ypos-=1;

return ref;

}


const Point operator--(Point &ref, int) //후위감소

{

const Point retObj(ref);

ref.xpos-=1;

ref.ypos-=1;


return retObj;

}


int main(void)

{

Point pos(5, 5);

++pos;


pos.Show();

--pos;

pos.Show();


(--(--pos)).Show();


Point cpy = pos++;

cpy.Show();

pos.Show();


cpy = pos--; //operator--(pos);

cpy.Show();

pos.Show();


// (cpy++)++; //컴파일 에러

return 0;

}





반환형에서의 const 선언과 const 객체


const Point retObj(*this);        //함수 내에서 retObj 의 변경을 막겠다라는 뜻!


# 반황형으로 선언된 const는 어떠한 의미를 지닐까?


=> operator-- 함수의 반환으로 인해서 생성되는 임시객체를 const 객체로 생성하겠다. 라는 의미가 된다.


ex) const Point pos(3, 4);

# pos 객체를 상수화해서 pos 객체에 저장된 값의 변경을 허용하지 않겠다는 뜻이다. 그래서 const객체인 pos를 대상으로는 const로 선언된 함수만 호출이 가능하다.

(( const 함수 내에서는 const 함수의 호출만 허용하도록 제한한다 )) => 즉, const 객체를 대상으로는 값의 변경능력을 지니는, const로 선언되지 않은 함수의 호출을 허용하지 않겠다는 뜻이다. 



 - 결론

# 이 예제에서 반환형으로 const를 선언한 이유!!!


=> 후위증가, 감소 연산은 전위증가, 감소와는 다르게 같은 라인에 2번이상 못쓴다. 이것이 C, C++의 연산 특성이다. 

그래서 이 특성을 그대로 반영하기 위하여 반환형에 const를 넣어준 것이다. 컴파일 에러가 발생할 수 있도록!!!

'Programming > C++' 카테고리의 다른 글

cout, cin 그리고 endl의 정체  (0) 2012.08.30
교환법칙 문제의 해결  (0) 2012.08.28
단항 연산자의 오버로딩  (0) 2012.08.27
연산자 오버로딩 (Operator Overloading)  (0) 2012.08.25
가상 상속 (Virtual Inheritance)  (0) 2012.08.22
Posted by scii
:

피연산자가 두 개인 이항 연산자와 피연산자가 한 개인 단항 연산자의 가장 큰 차이점은 피연산자의 개수이다.

그리고 이에 따른 연산자 오버로딩의 차이점은 매개변수의 개수에서 발견된다.


증가, 감소 연산자의 오버로딩



# this는 객체자신의 포인터 값을 의미하므로, *this는 객체 자신을 의미한다.


# ++(++pos); 는 소괄호 부분이 먼저 다음의 형태로 해석된다.

=> ++(pos.operator++()); 그 다음에 반환이 참조 값이므로 다음의 형태가 된다.

=> (pos의 참조 값).operator++();

# 그런데 pos의 참조 값을 대상으로 하는 연산은 pos 객체를 대상으로 하는 연산이기 때문에 결과적으로 위의 문장이 실행되면서 pos 객체의 멤버변수 값은 다시 1씩 증가한다.


# 정리하면, 위 예제의 operator++ 함수에서 객체 자신을 참조할 수 있는 참조 값을 반환하는 이유는 일반적인 ++ 연산자와 같은 형태의 연산이 가능하게 하기 위함이다.




Posted by scii
: