티스토리 뷰

반응형

객체간 대입연산의 비밀: 디폴트 대입 연산자

 

복사 생성자의 특징

-정의하지 않으면 디폴트 복사 생성자가 삽입된다.

-디폴트 복사 생성자는 멤버 대 멤버의 복사(얕은 복사)를 진행한다.

-생성자 내에서 동적 할당을 한다면, 그리고 깊은 복사가 필요하다면 직접 정의해야 한다.

 

대입 연산자의 특징

-정의하지 않으면 디폴트 대입 연산자가 삽입된다.

-디폴트 대입 연산자는 멤버 대 멤버의 복사(얕은 복사)를 진행한다

-연산자 내에서 동적 할당을 한다면, 그리고 깊은 복사가 필요하다면 직접 정의해야 한다.

 

복사 생성자는 생성되지 않은 객체에 대입

대입 연산자는 이미 초기화된 객체에 대입

 

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;

}

 

형 변환 연산자는 반환형을 표기하지 않고, 뒤에 변환할 것을 적는다.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
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
글 보관함
반응형