IS - A 관계


상속으로 클래스의 관계를 구성하기 위해서는 조건이 필요하다. 그리고 조건과 그에 따른 필요가 충족되지 않으면, 상속은 하지 않는 것만 못하다.


상속을 위한 기본 조건인 IS-A 관계의 성립.


무선 전화기는 일종의 전화기 입니다.

무선 전화기 is a 전화기


노트북 컴퓨터는 일종의 컴퓨터입니다.

노트북컴퓨터 is a 컴퓨터


즉, 상속관계가 성립되려면 기초 클래스와 유도 클래스간에 IS-A 관계가 성립해야 한다. 만약에 이 상속관계로 묶고자 하는 두 클래스가 IS-A 관계로 표현되지 않는다면, 이는 적절한 상속의 관계가 아닐 확률이 매우 높은 것이니, 신중한 판단이 필요하다.


#include <iostream>

#include <cstring>

using namespace std;


class Computer

{

    private:

        char owner[50];

    public:

        Computer(char* name)

        {

            strcpy(owner, name);

        }

        void Calculate(void) const

        {

            cout<<"요청 내용을 계산합니다."<<endl;

        }

};


class NotebookComp :public Computer

{

    private:

        int battery;

    public:

        NotebookComp(char* name, int chag)

            : Computer(name), battery(chag)

        {}

        void Charging() {battery+=5;}

        void UseBattery() {battery-=1;}

        void MovingCal()

        {

            if(GetBatteryInfo() < 1)

            {

                cout<<"충전이 필요합니다."<<endl;

                return;

            }

            cout<<"이동하면서 ";

            Calculate();

            UseBattery();

        }

        int GetBatteryInfo(void) {return battery;}

};


class TabletNotebook :public NotebookComp

{

    private:

        char penModel[50];

    public:

        TabletNotebook(char* name, int chag, char* pen)

            :NotebookComp(name, chag)

        {

            strcpy(penModel, pen);

        }

        void Write(char* penInfo)

        {

            if(GetBatteryInfo() < 1)

            {

                cout<<"충전필요"<<endl;

                return;

            }

            if(strcmp(penModel, penInfo) != 0)

            {

                cout<<"등록된 펜이 아니야"<<endl;

                return;

            }

            cout<<"필기내용 처리한다"<<endl;

            UseBattery();

        }

};


int main(void)

{

    NotebookComp nc("qwersd", 5);

    TabletNotebook tn("sdfdfdd", 5, "123");


    nc.MovingCal();

    tn.Write("123");


    return 0;

}


NotebookComp(노트북 컴퓨터)는 Computer(컴퓨터)이다.
TabletNotebook(타블렛 컴퓨터)는 NotebookComp(노트북 컴퓨터)이다.

다음의 관계도 성립이 된다.
TabletNotebook(타블렛 컴퓨터)는 Computer(컴퓨터)이다.

때문에 IS-A 관계의 관점에서만 보면 위 클래스들의 상속관계는 적절하다고 볼 수 있다.

UML(Unified Modeling Language) 의 상속 표현.

Computer
                                                       ↑   class NotebookComp :public Computer
NotebookComp
                                                              ↑   class TabletNotebook :public NotebookComp
TabletNotebook





HAS - A 관계

※ has-a 관계도 상속의 조건은 되지만 복합 관계로 이를 대신하는 것이 일반적.

is-a 관계 외에도 상속이 형성될만한 관계가 "소유의 관계(has-a)" 이다.
경찰이 총을 소유한다. 
경찰 has a 총.

#include <iostream>
#include <cstring>
using namespace std;

class Gun
{
private:
int bullet;
public:
Gun(int bnum) :bullet(bnum)
{ }
void Shut(void)
{
cout<<"BBANG!"<<endl;
bullet--;
}
};

class Police
{
private:
int handcuffs;
Gun* pistol;
public:
Police(int bnum, int bcuff)
: handcuffs(bcuff)
{
if(bnum > 0)
pistol = new Gun(bnum);
else
pistol = NULL;
}
void PutHandcuff(void)
{
cout<<"SNAP!"<<endl;
handcuffs--;
}
void Shut(void)
{
if(pistol==NULL)
cout<<"Hut BBAND!"<<endl;
else
pistol->Shut();
}
~Police(void)
{
if(pistol != NULL)
delete pistol;
}
};

int main(void)
{
Police pman(4, 3);
pman.Shut();
pman.PutHandcuff();

return 0;
}

상속으로 묶인 두 개의 클래스는 강한 연관성을 띤다. 즉, Gun 클래스를 상속하는 Police 클래스로는 총을 소유하는 경찰만 표현 가능하다. 하지만 바로 위의 예제에서는 멤버변수 pistol을 NULL로 초기화함으로써 권총을 소유하지 않은 경찰을 매우 간단히 표현하였다. 
그리고 만약 전기봉을 소유하는 경찰의 표현을 위해서 Police 클래스를 확장하는 것도 어렵지 않다. 전기봉을 표현하는 객체를 참조하는 멤버변수만 하나 추가하면 되기 때문이다.

※ 상속은 IS-A 관계의 표현에 매우 적절하다. 그리고 경우에 따라서는 HAS-A 관계의 표현에도(소유 관계의 표현에도) 사용될 수 있으나, 이는 프로그램의 변경에 많은 제약을 가져다 줄 수 있다.

Posted by scii
:

protected 선언


C++ 의 접근 제어 지시자에는 private, protected, public 이렇게 세가지가 존재한다. 그리고 이들이 허용하는 접근의 범위에는 다음의 관계가 있다.


private < protected < public


즉, public이 허용하는 접근의 범위가 가장 넓고, private이 허용하는 접근의 범위가 가장 좁다. 반면, protected는 그 중간 범위의 접근을 허용한다. 하지만 protected는 private과 매우 유사하다.


class A

{

private:

int num1;


protected:

int num2;


public:

int num3;

A(int n1, int n2, int n3)

:num1(n1), num2(n2), num3(n3)

{ }

void Show() const

{

cout<<num1<<num2<<num3<<endl;

}

};


이 상태에서는 private과 protected가 차이를 보이지 않는다. 둘 다 클래스의 외부에서는 접근이 불가능한 반면, 클래스 내부에서는 접근이 가능하기 때문이다. 

하지만, 이 클래스가 상속이 되면, 이야기는 달라진다.


class B :public A

{

public:

B(int n1, int n2, int n3)

:A(n1, n2, n3)

{ }

void ShowData() const

{

cout<<num1; // 컴파일 에러

cout<<num2; // 컴파일 OK

cout<<num3; // 컴파일 Ok

}

};


위의 클래스는 A 클래스를 상속하고 있다. 따라서 public으로 선언된 멤버변수 num3에 접근이 가능하지만 private으로 선언된 멤버변수 num1에는 접근이 불가능하다. 

여기서 protected는 "protected로 선언된 멤버변수는 이를 상속하는 유도 클래스에서 접근이 가능하다."


protected 멤버는 유도 클래스에서 접근이 가능하다. 그리고 이것이 private과 protected의 유일한 차이점이다!!


protected선언은 private과 public에 비해 그리 많이 사용되지 않는다. 물론 유도 클래스에게만 제한적으로 접근을 허용한다는 측면에서 유용하게 사용될 수 있는 키워드이다. 하지만 기본적으로는 기초 클래스와 이를 상속하는 유도 클래스 사이에서도 '정보 은닉'은 지켜지는 게 좋다.







세가지 형태의 상속


class B :public A

{

...

}

상속을 명시하는데 있어서 public이 사용되었음을 알 수 있다. 따라서 이러한 경우 A클래스는 "public으로 상속되었다." 라고 한다. 


class B :protected A        //A클래스가 protected로 상속되었다.

{

...

}


class B :private A            //A클래스가 private으로 상속되었다.

{

...

}


이렇듯 상속에는 세가지 형태의 상속이 존재한다. 그리고 키워드 public, protected, private은 멤버의 접근권한을 명시하는 용도로도 사용되지만, 상속의 형태를 명시하는 용도로도 사용이 된다.




proteced 상속


class B :protected A

{

...

}


'protected 상속'이 의미하는 바는 다음과 같다.


"protected보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다."


protected보다 접근범위가 넓은 멤버는 public 멤버뿐이니, protected 상속을 한 클래스는 public이 protected로 변경되어서 상속이 이루어진다.


class B :protected A

{

접근불가:

int num1;

protected:

int num2;

protected:

int num3;

};

그리고 중요한 것이 있는데 private은 접근불가이다. 




private 상속


class B :private A

{

...

}


"private보다 접근의 범위가 넓은 멤버는 private으로 변경시켜서 상속하겠다"


private보다 접근범위가 넓은 멤버는 protected와 public이니 모두 private으로 변경되어 상속이 이루어진다.


class B :private A

{

접근불가:

int num1;

private:

int num2;

private:

int num3;

};


그리고 만약, 다른 클래스가 이 클래스를 다음과 같이 다시 상속한다면...

class C :public B

{

...

}


C 클래스의 모든 멤버가 private(접근불가) 이기 대문에 다음과 같은 형태가 되어버린다.

class C :public B

{

접근불가:

int num1;

접근불가:

int num2;

접근불가:

int num3;

};


이렇듯, private 상속이 이뤄진 클래스를 다시 상속할 경우, 멤버함수를 포함하여 모든 멤버가 '접근불가'가 되기 때문에 사실상 의미 없는 상속이 되고 만다.




public 상속


"public보다 접근의 범위가 넓은 멤버는 public으로 변경시켜서 상속하겠다."


그런데 public보다 넓은 멤버는 public밖에 없다. 고로 "private을 제외한 나머지는 그냥 그대로 상속한다."


즉, private 멤버는 '접근불가'의 형태로 상속을 하지만 protected 멤버는 protected로, public 멤버는 public으로 상속이 진행된다.


※ 참고로 상속의 대부분은 public 상속이다.

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

상속을 이용한 프로그램  (0) 2012.06.21
상속을 위한 조건  (0) 2012.06.19
상속(inheritance)  (0) 2012.06.06
C++에서의 static  (0) 2012.05.30
friend  (0) 2012.05.26
Posted by scii
:

상속(inheritance)

Programming/C++ 2012. 6. 6. 20:19 |

상속은 적용이 중요한 문법이다. 적절할 때 선별적으로 적용할 수 있어야 한다.


#include <iostream>

#include <cstring>

using namespace std;


class PermenentWorker //데이터적 성격이 강한 클래스

{

    private:

        char name[100];

        int salary;


    public:

        PermenentWorker(char* name, int money)

            :salary(money)

        {

            strcpy(this->name, name);

        }

        int GetPay(void) const

        {

            return salary;

        }

        void ShowSalaryInfo(void) const

        {

            cout<<"name: "<<name<<endl;

            cout<<"salary: "<<GetPay()<<endl<<endl;

        }

};


class EmployeeHandler //기능적 성격이 강한 클래스(컨트롤 클래스)

{

    private:

        PermenentWorker* empList[50];

        int empNum;


    public:

        EmployeeHandler() :empNum(0)

{}

        void AddEmployee(PermenentWorker* emp) //새로운 직원정보 등록

        {

            empList[empNum++] = emp;

        }

        void ShowAllSalaryInfo(void) const         //모든 직원의 급여정보 출력

        {

            for(int i=0; i<empNum; i++)

                empList[i]->ShowSalaryInfo();

        }

        void ShowTotalSalary(void) const     //급여의 총액 출력

        {

            int sum=0;

            for(int i=0; i<empNum; i++)

                sum += empList[i]->GetPay();

            cout<<"salary sum: "<<sum<<endl;

        }

        ~EmployeeHandler()

        {

            for(int i=0; i<empNum; i++)

                delete empList[i];

        }

};


int main(void)

{

    EmployeeHandler handler;


    handler.AddEmployee(new PermenentWorker("asdf", 1000));

    handler.AddEmployee(new PermenentWorker("xcv", 2332));


    handler.ShowAllSalaryInfo();


    handler.ShowTotalSalary();

    return 0;

}



기능의 처리를 실제로 담당하는 클래스를 가리켜 '컨트롤(control) 클래스' 또는 '핸들러(handler) 클래스' 라 한다.


※ 컨트롤 클래스는 기능 제공의 핵심이 되기 때문에 모든 객체지향 프로그램에서 반드시 존재하는 클래스이다.




※ 멤버는 클래스가 정의될 때, 멤버의 초기화를 목적으로 정의된 생성자를 통해서 초기화하는 것이 가장 안정적이다. 그것이 비록 상속의 관계로 묶여있다 할지라도.


◆ 용어 정리 ◆


상위 클래스                                하위 클래스

기초(base) 클래스                      유도(derived) 클래스

슈퍼(super) 클래스                    서브(sub) 클래스

부모 클래스                               자식 클래스




#include <iostream>

using namespace std;


class Base

{

    private:

        int baseNum;

    public:

        Base(void) :baseNum(20)

        {

        cout<<"Base(void)"<<endl;

        }

        Base(int n) :baseNum(n)

        {

        cout<<"Base(int n)"<<endl;

        }

        void ShowBaseData()

        {

            cout<<baseNum<<endl;

        }

};


class Derived :public Base

{

    private:

        int derivedNum;

    public:

        Derived() :derivedNum(30)

        {

        cout<<"Derived()"<<endl;

        }

        Derived(int n) : derivedNum(n)

        {

        cout<<"Derived(int n)"<<endl;

        }

        Derived(int n1, int n2)

            : Base(n1), derivedNum(n2)

        {

            cout<<"Derived(int n1, int n2)"<<endl;

        }

        void ShowDerivedData()

        {

            ShowBaseData();

            cout<<derivedNum<<endl;

        }

};


int main(void)

{

    cout<<"case1......"<<endl;

    Derived dr1;

    dr1.ShowDerivedData();


    cout<<"case2........."<<endl;

    Derived dr2(12);

    dr2.ShowDerivedData();


    cout<<"case3..........."<<endl;

    Derived dr3(14, 15);

    dr3.ShowDerivedData();


    return 0;

}



위의 소스코드로 알 수 있는 점 두가지.

1. 유도 클래스의 객체 생성 과정에서 기초 클래스의 생성자는 100% 호출이 된다.

2. 유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출이 된다.


★ "클래스의 멤버는 해당 클래스의 생성자를 통해서 초기화해야 한다."




유도 클래스 객체의 소멸과정



위의 결과로 알 수 있는 사실.

1. 유도 클래스의 객체가 소멸될 때에는, 유도 클래스의 소멸자가 실행되고 난 다음에 기초 클래스의 소멸자가 실행된다.

2. 스택에 생성된 객체의 소멸순서는 생성순서와 반대이다.


※ 이러한 객체소멸의 특성 때문에 상속과 연관된 클래스의 소멸자는 "생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제한다" 는 원칙을 지켜서 정의해야 한다.




#include <iostream>

#include <cstring>

using namespace std;


class Person

{

    private:

        char* name;

        

    public:

        Person(char* name)

        {

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

            strcpy(this->name, name);

        }

        ~Person()

        {

            delete []name;

        }

        void WhatYourName() const

        {

            cout<<"My name is "<<name<<endl;

        }

};


class UnivStudent : public Person

{

    private:

        char* major;


    public:

        UnivStudent(char* name, char* major)

            :Person(name)

        {

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

            strcpy(this->major, major);

        }

        ~UnivStudent()

        {

            delete []major;

        }

        void WhoAreYou() const

        {

            WhatYourName();

            cout<<"My major is "<<major<<endl<<endl;

        }

};


int main(void)

{

    UnivStudent st1("Jeon", "Houdini");

    st1.WhoAreYou();


    UnivStudent st2("Seo", "Maya");

    st2.WhoAreYou();


    return 0;

}



Posted by scii
:

C++에서의 static

Programming/C++ 2012. 5. 30. 14:46 |

C언어에서의static


1. 전역변수에 선언된 static의 의미 -> 선언된 파일 내에서만 참조를 허용하겠다는 의미.

2. 함수 내에 선언된 static의 의미 -> 한번만 초기회되고, 지역변수와 달리 함수를 빠져나가도 소멸되지 않는다.




C++에서의 static


- static 멤버변수(클래스 변수)


static 멤버변수는 "클래스 변수"라고도 한다. 일반적인 멤버변수와 달리 클래스당 하나씩만 생성되기 때문이다.




멤버변수에 선언된 static 변수는 객체를 생성하건 생성하지 않건, 메모리 공간에 딱 하나만 할당이 되어서 공유되는 변수이다.

이 변수는 객체 내에 존재하는 것이 아니다. 변수는 외부에 있다. 다만 객체에게 멤버변수처럼 접근할 수 있는 권한을 줬을 뿐이다.


static 멤버변수 초기화


: static멤버변수는 생성자에서 초기화하기 못한다. 왜냐면, static 멤버변수는 객체기 생성될 때 동시에 생성되는 변수가 아니고, 이미 메모리 공간에 할당이 이뤄진 변수이다. 그래서 static 멤버변수의 초기화 문법은 다음거과 같이 별도로 정의되어있다. 

int SoSimple::simObjCnt=0;


이는 SoSimple 클래스의 static멤버변수 simObjCnt가 멤모리 공간에 저장될 때 0으로 초기화하라는 뜻이다.




14행: static 멤버변수는 항상 이렇게 초기화가 이뤄짐.


18행: 현재 객체를 하나도 생성하지 않은 상태이다 . 그럼에도 불구하고 클래스의 이름을 이용해서 static멤버변수에 접근하고 있다. 이는 멤버변수가 객체 내에 존재하지 않은을 증명하는 내용이다.

즉, public으로 선언된 static멤버는 이런 식으로 어디서든 접근이 가능하다.


23, 24행: sim1, sim2를 이용해서도 static멤버변수에 접근이 가능하다. 하지만 이러한 형태의 접근은 멤버변수에 접근하는 것과 같은 오해를 불러일으키기 때문에 별로 추천하지 않는다.


const static멤버변수


const를 사용하면 클래스에서 이런식으로 가능해짐.



※ const static 멤버변수는 클래스가 정의될 때 지정된 값이 유지되는 상수이기 때문에, 위 예제에서 보이는 바와 같이 초기화가 가능하도록 문법으로 정의하고 있다.




- static 멤버함수


: static 멤버함수 역시 그 특성이 static 멤버변수와 동일하다.


1. 선언된 클래스의 모든 객체기 공유한다.

2. public으로 선언이 되면, 클래스의 이름을 이용해서 호출이 가능하다.

3. 객체의 멤버로 존재하는 것이 아니다.


"static 멤버함수 내에서는 static 멤버변수와 static멤버함수만 호출이 가능하다."




키워드 mutable


const와 explicit 키워드 이 둘은 나름의 의미가 있으며, 매우 유용하게 사용되는 키워드들이다. 그런데 mutable 키워드는 사용의 빈도수가 낮은 , 아니 가급적 사용의 빈도수를 낮춰야 하는 키워드이다.


"const 함수 내에서의 값의 변경을 예외적으로 허용한다."



※ mutable의 과도한 사용은 C++에 있어서 그 중요성을 인정받은 키워드인 const의 선언을 의미 없게 만들어버린다.

Posted by scii
:

friend

Programming/C++ 2012. 5. 26. 01:16 |

- 클래스와 함수에 대한 friend 선언 - 


클래스의 friend 선언


* A 클래스가 B 클래스를 대상으로 friend 선언을 하면, B 클래스는 A 클래스의 private 멤버에 직접 접근이 가능하다.

* 단, A 클래스도 B 클래스의 private 멤버에 직접 접근이 가능하려면, B 클래스가 A 클래스를 대상으로 friend 선언을 해줘야 한다.





friend 선언은 언제 선언해야 할까?


: c++ 문법 중에서 논란이 되었던 것 중 하나가 바로 이 friend 선언이다. friend 선언은 객체지향의 대명사 중 하나인 '정보은닉' 을 무너뜨리는 문법이기 때문이다.


※ friend 선언은 지나치면 아주 위험할 수 있습니다. friend 선언은 필요한 상황에서 극히 소극적으로 사용해야 합니다. - 열혈강의 C++


◇ friend 선언이 좋은 약으로 사용되는 상황은, 연산자 오버로딩을 공부하면서이다.






함수의 friend 선언


: 전역함수를 대상으로도, 클래스의 멤버함수를 대상으로도 friend 선언이 가능하다. 물론 friend 선언된 함수는 자신이 선언된 클래스의 private 영역에 접근이 가능하다.




friend void ShowPointPos(const Point&);

이 선언에는 friend 선언 이외에, 다음의 함수원형 선언이 포함되어 있다.


void ShowPointPos(const Point&);

따라서 friend 선언을 위해서 별도의 함수원형을 선언할 필요는 없다.

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

상속(inheritance)  (0) 2012.06.06
C++에서의 static  (0) 2012.05.30
const 객체와 const 객체의 특성 그리고 const 함수 오버로딩  (0) 2012.05.26
복사 생성자의 호출시점  (0) 2012.05.25
깊은 복사 & 얕은 복사  (0) 2012.05.24
Posted by scii
:

const 객체와 const 객체의 특성


객체도 상수화할 수 있다.

const Simple sim(2);


그리고 이렇게 객체에 const 선언이 붙게 되면, 이객체를 대상으로는 const 멤버함수만 호출이 가능하다. 이는 객체의 const 선언이 다음의 의미를 갖기 때문이다.

"이 객체의 데이터 변경을 허용하지 않겠다!"


물론 const로 선언되지 않은 함수 중에도 데이터를 변경하지 않는 함수가 있을 수 있다. 하지만 변경시킬 능력이 있는 함수는 아예 호출을 허용하지 않는 것이다.




멤버변수에 저장된 값을 수정하지 않는 함수는 가급적 const로 선언해서, const객체에서도 호출이 가능하도록 할 필요가 있다. const 선언은 많을수록 좋다.






const와 함수 오버로딩


: 함수의 오버로딩이 성립하려면 매개변수의 수나 자료형이 달라야 한다. 하지만 다음과 같이 const의 선언유무도 함수 오버로딩의 조건에 해당이 된다.


void SimpleFunc() {...}

void SimpleFunc() const {...}




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

C++에서의 static  (0) 2012.05.30
friend  (0) 2012.05.26
복사 생성자의 호출시점  (0) 2012.05.25
깊은 복사 & 얕은 복사  (0) 2012.05.24
explicit  (0) 2012.05.23
Posted by scii
:

복사 생성자의 호출시점

복사 생성자의 호출횟수는 프로그램의 성능과도 관계가 있기 때문에, 호출의 시기를 이해하는 것은 매우 중요하다.


복사 생성자가 호출되는 시점은 크게 세가지로 구분할 수 있다.


첫번째, 기존에 생성된 객체를 이용해서 새로운 객체를 초기화하는 경우.

두번째, Call-By-Value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우.

세번째, 객체를 반환하되 참조형으로 반환하지 않는 경우.


위의 세가지는 "객체를 새로 생성해야 한다. 단, 생성과 동시에 동일한 자료형의 객체로 초기화해야 한다." 는 공통점을 지닌다.


ex1) Simple obj2 = obj1;

: obj2 객체가 생성되면서 (obj2를 위한 메모리 공간이 할당되면서) 초기화도 이뤄진다.


ex2) Simple Func(Simple ob)

{

.......

}

int main(void)

{

Simple obj;

Simple Func(obj);

}

: ob 객체가 생성되면서 전달되는 인자로 초기화가 이뤄진다.


ex3) Simple Func(Simple ob)

{

.............

return ob;

}

: return 문이 실행되는 순간, Simple 객체를 위한 메모리 공간이 할당되고, 이 공간에 할당된 객체는 반환되는 객체 ob의 내용으로 초기화가 이뤄진다.






복사 생성자를 통한 초기화




32행: 23행에 선언된 매개변수 ob 의 복사 생성자가 호출되면서, 인자로 obj가 전달된다.






35행: Func 함수가 반환한 객체를 대상으로 AddNum 함수를 호출하고, 이어서 AddNum 함수가 반환하는 참조 값을 대상으로 ShowData 함수를 호출하고 있다.






임시객체


: 임시객체도 임시변수와 마찬가지로 임시로 생성되었다가 소멸되는 객체이다.


- 임시객체를 직접 생성하는 방법 -


위의 예제에서 내릴 수 있는 결론 두 가지

1. 임시객체는 다음 행으로 넘어가면 바로 소멸되어 버린다.

2. 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.


클래스 외부에서 객체의 멤버함수를 호출하기 위해 필요한 것은 다음 세 가지 중 하나이다.

1. 객체에 붙여진 이름

2. 객체의 참조 값

3. 객체의 주소 값


Temporary(200).ShowTempInfo();

임시객체가 생성된 위치에는 임시객체의 참조 값이 반환된다. 즉, 위 문장의 경우 먼저 임시객체가 생성되면서 다음의 형태가 된다.

(임시객체의 참조 값).ShowTempInfo();


그래서 이어서 멤버함수의 호출이 가능한 것이다.


또한 이렇듯 "참조 값"이 반환되기 때문에 다음과 같은 문장의 구성도 가능하다

const Temporary &ref = Temporary(300);


위의 경우는 임시객체 생성시 반횐되는 "참조 값"이 차모자 ref에 전달되어, ref가 임시객체를 참조하게 된다.


※ 임시객체는 메모리에 저장되고, 그 객체의 참조 값이 반환된다.






Simple tempRef = Func(obj);


언뜻 보면, tempRef라는 새로운 객체를 생성해서, 반환되는 객체를 가지고 대입연산을 진행하는 것처럼 보인다. 그러나 위의 출력결과에서는 추가로 객체를 생성하지 않고, 반환되는 임시객체에 tempRef라는 이름을 할당하고 있음을 보여준다(객체의 생성 수를 하나 줄여서 효율성을 높이기 위해서).

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

friend  (0) 2012.05.26
const 객체와 const 객체의 특성 그리고 const 함수 오버로딩  (0) 2012.05.26
깊은 복사 & 얕은 복사  (0) 2012.05.24
explicit  (0) 2012.05.23
복사 생성자(Copy Constructor)  (0) 2012.05.22
Posted by scii
:

얕은 복사(Shallow Copy)


디폴트 복사 생성자는 멤버대 멤버의 복사를 진행한다. 그리고 이러한 방식의 복사를 가리켜 '얕은 복사(shallow copy)' 라 하는데, 이는 멤버변수가 힙의 메모리 공간을 참조하는 경우에 문제가 된다.




디폴트 복사 생성자는 멤버 대 멤버의 단순 복사를 진행하기 때문에 복사의 결과로 하나의 문자열을 두 개의 객체가 동시에 참조하는 꼴을 만들어버린다. 그리고 이로 인해서 객체의 소멸과정에서 문제가 발생한다.

만약에, man2가 소멸이 된다고 치면, man1도 소멸이 될 것이다. 하지만 name이 참조하는 문자열은(힙 영역) 이미 소멸된 상태이다. 그런데 man1에서 또 다시 delete []name;을 통해 힙 영역에 접근에 delete를 하려고 하니까 문제가 발생하는 것이다.


& 이미 지워진 문자열을 대상으로 delete연산을 하기 때문에 문제가 된다. 따라서 복사 생성자를 정의할 때에는 이러한 문제가 발생하지 않도록 신경을 써야 한다.






깊은 복사(Deep Copy)


: 얕은 복사의 문제점은 힙 영역이였다. 이 문제를 해결하려면 힙 영역을 따로 하나 더 만들면 되는것이다. 그러면 객체 별로 각각의 문자열을 촘조하기 때문에 위에서 언급한 객체 소멸과정에서의 문제는 발생하지 않는다.

이러한 형태의 복사를 가리켜 "깊은 복사(deep copy)" 라 한다. 멤버뿐만 아니라 포인터로 참조하는 대상까지 깊게 복사한다는 뜻이다.


위에 소스코드에서

Person(const Person& copy) : (copy.age)

{

name = new char[strlen(copy.name)+1];

strcpy(name, copy.name);

}


이 생성자만 추가해주면 문제가 발생하지 않는다.



위의 복사 생성자가 하는 일은 다음 두가지이다.


1. 멤버변수 age의 멤버 대 멤버 복사.

2. 메모리 공간 할당후 문자열 복사, 그리고 할당된 메모리의 주소 값을 멤버 name에 저장.



- 복사 생성자의 또 다른 예 - 

#include <iostream>

#include <cstring>

using namespace std;


namespace COMP_POS

{

enum {CLERK, SENIOR, ASSIST, MANAGER};


void Select(int sel)

{

switch(sel)

{

case CLERK:

cout<<"사원"<<endl; break;

case SENIOR:

cout<<"주임"<<endl; break;

case ASSIST:

cout<<"대리"<<endl; break;

case MANAGER:

cout<<"과장"<<endl; break;

}

}

}


class NameCard

{

char * name;

char * company;

char * phone;

int position;


public:

NameCard(char* name, char* company, char* phone, int pos) //생성자

:position(pos)

{

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

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

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


strcpy(this->name, name);

strcpy(this->company, company);

strcpy(this->phone, phone);

}

explicit NameCard(NameCard &copy) //복사 생성자

:position(copy.position)

{

name = new char[strlen(copy.name)+1];

company = new char[strlen(copy.company)+1];

phone = new char[strlen(copy.phone)+1];


strcpy(name, copy.name);

strcpy(company, copy.company);

strcpy(phone, copy.phone);

}


void ShowNameCardInfo(void) const

{

cout<<"이름: "<<name<<endl;

cout<<"회사: "<<company<<endl;

cout<<"전화번호: "<<phone<<endl;

cout<<"직급: "; COMP_POS::Select(position);

cout<<endl;

}


~NameCard() //소멸자

{

delete []name;

delete []company;

delete []phone;

cout<<"Called Destructor"<<endl;

}

};


int main(void)

{

NameCard manClerk("lee", "ABC", "010-2323-1233", COMP_POS::CLERK);

NameCard copy1(manClerk);

NameCard manSENIOR("HaHo", "BOD", "010-7656-1235", COMP_POS::SENIOR);

NameCard copy2(manSENIOR);


copy1.ShowNameCardInfo();

copy2.ShowNameCardInfo();

return 0;

}


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

const 객체와 const 객체의 특성 그리고 const 함수 오버로딩  (0) 2012.05.26
복사 생성자의 호출시점  (0) 2012.05.25
explicit  (0) 2012.05.23
복사 생성자(Copy Constructor)  (0) 2012.05.22
this 포인터  (0) 2012.05.21
Posted by scii
: