class ThisClass
{
private:
int num;
public:
void thisFunc(int num)
{
this->num = 207; //멤버변수 num에 207이 저장됨.
num = 105;
}
}
thisFunc
함수 내에서 num은 매개변수 num을 의미하게 되므로, num
이라는 이름만으로느느 멤버변수에 접근을 못하는데, 이때 this 포인터를 활용하면 멤버변수에 접근할 수 있음.
객체 자신을 참조할 수 있는 참조자. this 포인터를 이용해서 객체가 자신의 참조에 사용할 수 있는 참조자의 반환무늘 구성할 수 있음!
#include <iostream>
using namespace std;
class SelfRef
{
private:
int num;
public:
SelfRef(int n): num(n)
{
cout << "객체 생성" << endl;
}
SelfRef& adder(int n)
{
num+=n;
return *this;
}
SelfRef& showTwoNumber(void)
{
cout << num << endl;
return *this;
}
}
int main(void)
{
SelfRef obj(3);
SelfRef &ref = obj.Adder(2);
obj
obj.showTwoNumber();
ref.showTwoNumber();
ref.adder(1).showTwoNumber().adder(2).showTwoNumber();
return (0);
}
객체 생성
5
5
6
8
int num = 20;
int &ref = num;
는 다음과 같다.
int num(20);
int &ref(num);
위 두 방식은 결과적으로 동일하다. C++에서는 위 두 가지 초기화 방식을 동시에 지원하고 있다.
이제 객체 생성에 대해 생각해보자.
...
class Simple
{
private:
int num1;
int num2;
public:
Simple(int n1, int n2): num1(n1), num2(n2)
{
}
void showSimple(void)
{
cout << num1 << endl;
cout << num2 << endl;
}
}
위에 정의된 클래스로 다음을 실행해보자.
int main(void)
{
Simple sim1(15, 20);
Simple sim2 = sim1;
sim2.showSimple();
return (0);
}
Simple sim2 = sim1;
는 객체의 생성 및 초기화를 연상시킨다.
즉, sim2 객체를 새로 생성해서, 객체 sim1과 sim2간의 멤버 대 멤버 복사가 일어난다고 예상해 볼 수 있다.
그런데 실제로 그러한 일이 일어난다.
다음 두 문장이 동일한 의미로 해석되듯이,
int num1 = num2;
int num1(num2);
다음 두 문장도 동일한 의미로 해석이 된다.
Simple sim2 = sim1;
Simple sim2(sim1);
이때, 과연 sim2는 어떠한 과정을 거쳐서 생성되는 것일까?
생성자 호출 관점에서 이를 생각해보자.
Simple sim2(sim1);
위 객체 생성문에서 호출하고자 하는 생성자는 다음과 같이 Simple 객체를 인자로 받을 수 있는 생성자이다.
Simple(Simple &ref)
{
...
}
그리고 다음의 문장도
Simple sim2 = sim1;
실은 Simple sim2(sim1);
로 묵시적으로 변환이 되어서 객체가 생성되는 것이다.
그런데 앞에 정의된 Simple 클래스에서는 이러한 유형의 생성자가 정의되어 있지 않았다.
이 때 호출되는 생성자의 이름은 디폴트 복사 생성자이다.
이 생성자도 똑같이 디폴트 생성자를 오버로딩한 형태인데 왜 특별한 이름이 붙었을까?
그건 복사 생성자가 호출되는 시점이 다른 일반 생성자와 차이가 있기 때문이다. 이것은 조금 뒤에 알아보고, 지금은 왜 디폴트 복사 생성자가 있는데 굳이 복사 생성자를 정의해주는지 알아보자.
cf) 멤버 대 멤버의 복사에 사용되는 원본을 변경시키는 것은 복사의 개념을 무너뜨리는 행위가 되니, 키워드 const를 삽입해서 이러한 실수를 막아주자.
복사 생성자를 정의하지 않으면, 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입된다.
Class Simple
{
private:
int num1;
int num2;
public:
Simple(int n1, int n2): num1(n1), num2(n2)
{
}
...
};
위 클래스와 같이 복사생성자가 정의되어 있지 않으면, 다음 클래스와 완전히 동일하다.
Class Simple
{
private:
int num1;
int num2;
public:
Simple(int n1, int n2): num1(n1), num2(n2)
{
}
Simple(cosnt Simple &ref): num1(ref.n1), num(ref.n2)
{
}
...
};
또한
Simple sim2 = sim1;
는 자동으로
Simple sim2(sim2);
으로 묵시적으로 변환되어, 복사생성자가 호출된다. 이러한 묵시적 변환이 맘에 들지 않는다면 explicit 키워드를 쓰면 된다.
바로 위의 클래스에서 복사생성자 앞에 explicit을 붙이면
explicit Simple(const Simple &ref): num1(ref.n1), num2(ref.n2)
{
}
대입 연산자를 이용한 객체의 생성 및 초기화는 불가능하다.
이제 디폴트 복사 생성자를 사용할 때 생길 수 있는 문제에 대해 알아보자.
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
private:
char *name;
int age;
public:
Person(const char *name, int age)
{
int len = strlen(name)+1;
this->name = new char[len];
strcpy(this->name, name);
this->age = age;
}
void showPersonInfo() const
{
cout << "이름 : " << this->name << endl;
cout << "나이 : " << this->age << endl;
}
~Person()
{
delete[] this->name;
cout << "called destructor" << endl;
}
};
int main(void)
{
Person man1("hyeonkim", 30);
Person man2 = man1;
man1.showPersonInfo();
man2.showPersonInfo();
return (0);
}
위 코드에서 Person man2 = man1;
문장을 통해 디폴트 복사생성자가 호출됐고, 이때 멤버 대 멤버의 단순 복사(얕은 복사)가 진행되었다. 즉, 하나의 문자열을 두 객체가 동시에 참조하는 꼴이 된다. 그러므로 객체의 소멸 과정에서 문제가 발생한다.
그러므로 깊은 복사를 원한다면 복사 생성자를 새로 정의하자.
Person(const Person &ref): age(ref.age)
{
this->name = new char[strlen(ref.name) + 1];
strcpy(this->name, ref.name);
}
(추가 정리 예정)