티스토리 뷰
객체간 대입연산의 비밀: 디폴트 대입 연산자
복사 생성자의 특징
-정의하지 않으면 디폴트 복사 생성자가 삽입된다.
-디폴트 복사 생성자는 멤버 대 멤버의 복사(얕은 복사)를 진행한다.
-생성자 내에서 동적 할당을 한다면, 그리고 깊은 복사가 필요하다면 직접 정의해야 한다.
대입 연산자의 특징
-정의하지 않으면 디폴트 대입 연산자가 삽입된다.
-디폴트 대입 연산자는 멤버 대 멤버의 복사(얕은 복사)를 진행한다
-연산자 내에서 동적 할당을 한다면, 그리고 깊은 복사가 필요하다면 직접 정의해야 한다.
복사 생성자는 생성되지 않은 객체에 대입
대입 연산자는 이미 초기화된 객체에 대입
int main(void)
{
Point pos1(5, 7);
Point pos2=pos1;
}
복사생성자
int main(void)
{
Point pos1(5, 7);
Point pos2(9, 10);
pos2=pos1;
}
대입 연산자
디폴트 대입 연산자의 문제점
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
private:
char* name;
int age;
public:
Person(char* myname, int myage)
{
int len = strlen(myname) + 1;
name = new char[len];
strcpy(name, myname);
age = myage;
}
void ShowPosition() const
{
cout << "이름: " << name << endl;
cout << "나이: " << age << endl;
}
~Person()
{
delete []name;
cout << "called destructor!" << endl;
}
};
int main(void)
{
Person man1("Lee dong woo", 29);
Person man2("Yoon ji yul", 22);
man2 = man1;
man1.ShowPosition();
man2.ShowPosition();
}
소멸자가 한 번만 호출되었다.
문자열을 얕은 복사하기에, 지워진 문자열을 중복 소멸하는 문제가 발생한다.
문자열 Yoon ji yul을 가리키던 주소 값을 잃게 되면서 메모리 누수가 발생한다.
-> 깊은 복사가 진행하도록 정의한다
-> 메모리 누수가 발생하지 않도록, 깊은 복사에 앞서 메모리 해체의 과정을 거친다.
Person& operator=(const Person& ref)
{
delete []name;
int len=strlen(ref.name)+1;
name=new char[len];
strcpy(name, ref.name);
age.ref.age;
return *this;
}
상속 구조에서의 대입 연산자 호출
'유도 클래스의 대입 연산자에는 아무런 명시를 하지 않으면, 기초 클래스의 대입 연산자가 호출되지 않는다'
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
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; }
First& operator=(const First& ref)
{
cout << "First& operator=()" << endl;
num1 = ref.num1;
num2 = ref.num2;
return *this;
}
};
class Second : public First
{
private:
int num3, num4;
public:
Second(int n1,int n2, int n3, int n4)
:First(n1,n2),num3(n3),num4(n4)
{}
void ShowData()
{
First::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)
{
Second ssrc(111, 222, 333, 444);
Second scpy(0, 0, 0, 0);
scpy = ssrc;
scpy.ShowData();
return 0;
}
결과
First& operator=()
111, 222
333, 444
대입시 기초 클래스의 대입연산자까지 호출된다.
주석 부분을 제거하면
Second& operator=()
0, 0
333, 444
-> 유도 클래스의 대입 연산자 정의에서 기초 클래스의 대입 연산자 호출문을 삽입하지 않으면, 기초 클래스의 대입 연산자는 호출되지 않아서, 기초 클래스의 멤버변수는 멤버 대 멤버의 복사 대상에서 제외된다.
주석부분에
First::operator=(ref);를 삽입한다!
이니셜라이저가 성능 향상에 도움을 준다고 했던 것을 기억하나요?
BBB(const AAA& ref): men(ref){ }
이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 구성된다.
11-2 배열의 인덱스 연산자의 오버로딩
배열보다 나은 배열 클래스
기본 배열은 경계검사를 하지 않는다.
arr[3]={1, 2, 3}
arr[-1]
arr[-2]
arr[3]
arr[4]
컴파일이 되고 에러가 뜨지 않는다.
int operator[] (int ix)
{
if(idx<0||idx>arrlen)
{
cout<<"Array index out of bound exception"<<endl;
exit(1);
}
}
배열 클래스로 정의하면 값의 이상치 입력을 막을 수 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdlib>
using namespace std;
class BoundCheckIntArray
{
private:
int* arr;
int arrlen;
BoundCheckIntArray(const BoundCheckIntArray&arr){}
BoundCheckIntArray& operator=(const BoundCheckIntArray& arr){}
public:
BoundCheckIntArray(int len) :arrlen(len)
{
arr = new int[len];
}
int& operator[](int idx)
{
if (idx < 0 || idx >= arrlen)
{
cout << "Array index out of bound exception" << endl;
exit(1);
}
return arr[idx];
}
int GetArrLen() const { return arrlen; }
~BoundCheckIntArray() { delete[] arr; }
};
void ShowAllData(const BoundCheckIntArray& ref)
{
int len = ref.GetArrLen();
for (int idx = 0; idx < len; idx++)
cout << ref[idx] << endl;
}
int main(void)
{
BoundCheckIntArray arr(5);
for (int i = 0; i < 5; i++)
arr[i] = (i + 1) * 11;
ShowAllData(arr);
return 0;
}
이 코드는 컴파일이 되지 않는다.
void ShowAllData(const BoundCheckIntArray& ref)
{
int len = ref.GetArrLen();
for (int idx = 0; idx < len; idx++)
cout << ref[idx] << endl;
}
매개변수가 const이기 때문이다. 이를 해결하려면 operator[] 도 const로 선언하면 되지만, 그러면 값을 아예 입력을 받을 수 없다.
operator 함수를 const선언을 이용해 오버로딩 하자.
int operator[] (int idx) const
int operator[] (int idx)
객체 저장을 위한 배열 클래스의 정의
11-3 그 이외의 연산자 오버로딩
new 연산자 오버로딩에 대한 상세한 이해
new와 delete 연산자 오버로딩은 앞선 오버로딩과 다르다.
new 연산자가 하는 일
1) 메모리 공간의 할당
2) 생성자의 호출
3) 할당하고자 하는 자료형에 맞게 변환된 주소 값의 형 변환
-> 이 중 1)번만이 오버로딩이 하는 일이다.
void*operator new(size_t size)
-> 반드시 이 형태로만 오버로딩이 가능하다!
void* operator new(size_t size)
{
void*adr=new char[size];
return adr;
}
delete 연산자의 오버로딩에 대한 상세한 이해와 예제
void operator delete(void*adr)
{
delete []adr;
}
객체생성이 안 됐는데, 어떻게 멤버함수의 호출이 가능했을까?
->operator new함수와 operator delete 함수는 static 함수이다.
operator new & operator new[]
operator new
operator new[]
는 호출시점 외에 별 차이가 없다.
포인터 연산자 오버로딩
-> 포인터가 가리키는 객체의 멤버에 접근
* 포인터가 가리키는 객체에 접근
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdlib>
using namespace std;
class Number
{
private:
int num;
public:
Number(int n):num(n){}
void ShowData() { cout << num << endl; }
Number* operator->()
{
return this;
}
Number& operator*()
{
return *this;
}
};
int main(void)
{
Number num(20);
num.ShowData();
(*num).ShowData();
return 0;
}
스마트포인터(Smart Pointer)
포인터 역할을 하는 객체를 뜻한다.
-> 구하는 것이 아니라 구현해야할 대상.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdlib>
using namespace std;
class Point
{
private:
int xpos, ypos;
public:
Point(int x = 0, int y = 0) :xpos(x), ypos(y)
{
cout << "Point 객체 생성" << endl;
}
~Point()
{
cout << "Point 객체 소멸" << endl;
}
void SetPos(int x, int y)
{
xpos = x;
ypos = y;
}
friend ostream& operator<<(ostream& os, const Point& pos);
};
ostream& operator<<(ostream& os, const Point& pos)
{
os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
return os;
}
class SmartPtr
{
private:
Point* posptr;
public:
SmartPtr(Point*ptr):posptr(ptr)
{}
Point& operator*() const
{
return *posptr;
}
Point* operator->() const
{
return posptr;
}
~SmartPtr()
{
delete posptr;
}
};
int main(void)
{
SmartPtr sptr1(new Point(1, 2));
SmartPtr sptr2(new Point(2, 3));
SmartPtr sptr3(new Point(4, 5));
cout << *sptr1;
cout << *sptr2;
cout << *sptr3;
sptr1->SetPos(10, 20);
sptr2->SetPos(30, 40);
sptr3->SetPos(50, 60);
cout << *sptr1;
cout << *sptr2;
cout << *sptr3;
return 0;
}
정말로 포인처럼 동작하면서 delete 연산이 자동으로 이루어졌다.
() 연산자의 오버로딩과 펑터(Functor)
함수의 호출에 사용되는 ()도 연산자다. 이 연산자를 오버로딩하면 객체를 함수처럼 사용할 수 있다.
add(2, 4);
->adder.operator()(2, 4);
int형, double형, Point 형 모두 오버로딩을 정의해서 계산할 수 있다.
펑터의 위력
-> 함수의 유연성을 부여한다.
하나의 호출로 오름차순으로 할 수도 있고, 내림차순으로 할 수도 있고 다양한 정의가 가능하다.
임시객체로의 자동 형 변환
operator int ()
{
return num;
}
형 변환 연산자는 반환형을 표기하지 않고, 뒤에 변환할 것을 적는다.
'프로그래밍 > 윤성우의 열혈 C++' 카테고리의 다른 글
Chapter 13 템플릿(Template) 1 (0) | 2022.03.15 |
---|---|
Chapter 12 String 클래스의 디자인 (0) | 2022.03.15 |
Chapter 09 가상(virtual)의 원리와 다중상속 (0) | 2022.03.12 |
08 상속과 다형성 (0) | 2022.03.10 |
Chapter 07 상속(Inheritance)의 이해 (0) | 2022.03.09 |
- Total
- Today
- Yesterday
- 여인권
- 인프런
- 백준
- 통계
- 윤성우
- 뇌와행동의기초
- 통계학
- 류근관
- 오블완
- 사회심리학
- 열혈프로그래밍
- 인지부조화
- 일본어문법무작정따라하기
- K-MOOC
- 일문따
- 파이썬
- c++
- 데이터분석
- C/C++
- 심리학
- 코딩테스트
- 회계
- Python
- jlpt
- EBS
- 보세사
- C
- 일본어
- 티스토리챌린지
- stl
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |