이것이 C++이다 : Chapter4 내용정리

JB·2022년 6월 25일
0
post-thumbnail

복사생성자와 임시객체

→ 언제 호출되는 지에 대해서 알아야함! 성능, 오류와 관련되어 있기 때문에 잘 알아두어야 함!

복사생성자


객체의 복사본을 생성할 때 호출되는 생성자

“새롭게 생성되는 객체가 원본 객체와 내용이 같으면서도 완전한 독립성을 가진다”

클래스 이름(const 클래스이름 &rhs)
//const는 r-value reference 를 쓸 때 원칙처럼 사용! - 원본손상 예방
//EMC++인가 Google Style Guide에 나왔는데 기억 안남...
#include <iostream>
using namespace std;

class CMyData{
public:
    CMyData(){
        cout<<"CMydata()"<<endl;
    }

    CMyData(const CMyData &rhs){
        this->m_nData = rhs.m_nData;
				//생성되는 객체를 명시하고자 l-value에
				//의도적으로 this->() 표현
        cout<<"CMyDATA(const CMydata &)"<<endl;
    }

    int GetData(void) const {return m_nData;}
    void SetData(int nParam) {m_nData = nParam;}

private:
    int m_nData = 0;

};

int main(){
    CMyData a; //선언&디폴트
    a.SetData(10);

    CMyData b(a); //복사 생성자 선언 및 정의
    cout<<b.GetData()<<endl;

    a.SetData(20);

    cout<<a.GetData()<<endl;
    cout<<b.GetData()<<endl;

    return 0;
}

/* 출력결과
CMydata()
CMyDATA(const CMydata &)
10
20
10
*/

함수 호출과 복사 생성자

함수가 호출될 때 매개변수 param은 caller의 a를 복사해서 사용함

→ 두 개 만들고 복사까지 해야됨 → 성능ㅠㅠ

해결방법

  1. 복사 생성자를 삭제해버림 - 클래스 생성자에서 r-value reference를 통한 생성을 아예 없애버림

    CTestData(const CTestDate &rhs) = delete;
  1. 참조자를 사용함

    #include <iostream>
    using namespace std;
    
    class CTestData{
    public:
        CTestData(int nParam):m_nData(nParam){
            cout<<"CTestData(int)"<<endl;
            cout<<this->m_nData<<endl;
        }
    
        CTestData(const CTestData &rhs) : m_nData(rhs.m_nData){
            cout<<"CTestData(CTestData &)"<<endl;
        }
    
        int GetData(void) const {return m_nData;}
        void SetData(int nParam) {
            m_nData = nParam;
            cout<<this->m_nData<<endl;
        }
    
    private:
        int m_nData = 0;
    };
    
    void TestFunc(const CTestData &param){
        //a 객체를 복사생성하므로 param을 참조자로 불러옴.
        //대신 원본 훼손을 막기 위해 const를 붙여주는 게 원칙.
        // 그러면 SetData()가 안먹힘!!!
        cout<<"TestFunc()"<<endl;
        //param.SetData(20);
        cout<<param.GetData()<<endl;
    }
    
    int main(){
        cout<<"*****Begin*****"<<endl;
        CTestData a(10);
        TestFunc(a);
    
        cout<<"a : "<<a.GetData()<<endl;
        cout<<"*****End*****"<<endl;
    
        return 0;
    }
    
    /* 출력결과
    *****Begin*****
    CTestData(int)
    10
    TestFunc()
    10
    a : 10
    *****End*****
    */

깊은 복사와 얕은 복사

복사를 잘 사용하자…

#include <iostream>
using namespace std;

class CMyData{
public:
    CMyData(int nParam){
        m_pData = new int;
        *m_pData = nParam;
    }

    CMyData(const CMyData &rhs){ //복사 생성자를 선언하여 deep copy를 만들어줌!
        cout<<"CMyData(const CMydata &)"<<endl;
        m_pData = new int;
        *m_pData = *rhs.m_pData;
    }

    ~CMyData(){
        delete m_pData;
    }

    int GetData(){
        if(m_pData != NULL){
            return *m_pData;
        }
        return 0;
    }

    void SetData(int nParam) {*m_pData = nParam;}

private:
    int *m_pData = nullptr;
};

int main(){
    CMyData a(10);
    CMyData b(a);
    cout<<a.GetData()<<endl;
    cout<<b.GetData()<<endl;

    a.SetData(20);
    cout<<a.GetData()<<endl;
    cout<<b.GetData()<<endl;
}

/* 출력 결과
CMyData(const CMydata &)
10
10
20
10
*/

대입연산자

단순 대입(=)을 사용하면 shallow copy가 일어남.

따라서 연산자 다중정의(overload)를 통해서 deep copy를 해주어야 함.

operater= 를 통해서 ‘=’ 기본 연산자를 재정의할 수 있음!

#include <iostream>
using namespace std;

class CMyData{
public:
    CMyData(int nParam){
        m_pData = new int;
        *m_pData = nParam;
    }

    CMyData(const CMyData &rhs){
        cout<<"CMyData(const CMydata &)"<<endl;
        m_pData = new int;
        *m_pData = *rhs.m_pData;
    }

    ~CMyData(){
        delete m_pData;
    }

    CMyData& operator=(const CMyData &rhs){
        *m_pData = *rhs.m_pData;
        return *this;
    }

    int GetData(){
        if(m_pData != NULL){
            return *m_pData;
        }
        return 0;
    }

    void SetData(int nParam) {*m_pData = nParam;}

private:
    int *m_pData = nullptr;
};

int main(){
    CMyData a(10);
    CMyData b(20);
    CMyData c(30);
    cout<<a.GetData()<<endl;
    cout<<b.GetData()<<endl;
    cout<<c.GetData()<<endl;

    b=a;
    c.operator=(a);

    cout<<a.GetData()<<endl;
    cout<<b.GetData()<<endl;
    cout<<c.GetData()<<endl;

    a.SetData(100);

    cout<<a.GetData()<<endl;
    cout<<b.GetData()<<endl;
    cout<<c.GetData()<<endl;
}

/* 출력 결과
10
20
30
10
10
10
100
10
10
*/



묵시적 변환


변환 생성자(Conversion Constructor) :

생성자의 매개변수가 하나이고 해당 매개변수의 형을 출력하는 클래스의 경우, 매개변수 형식과 매우 높은 호환성을 가지므로 모호성이 발생함(int 1개를 생성자 매개변수로 하고, 멤버변수도 int 하나일 경우, int와 클래스가 헷갈릴 수 있음)

→ 불필요한 임시 객체를 만들어냄으로써 프로그램의 효율을 갉아먹음.

#include <iostream>
using namespace std;

class CTestData{
public:
    CTestData(int nParam):m_nData(nParam){
        cout<<"CTestData(int)"<<endl;
    }

    CTestData(const CTestData &rhs) : m_nData(rhs.m_nData){
        cout<<"CTestData(const CTestData &)";
    }

    ~CTestData(){
        cout<<"~CTestData()"<<endl;
    }

    int GetData() const {
        return m_nData;
    }

    void SetData(int nParam){
        m_nData = nParam;
    }

private :
    int m_nData = 0;
};

void TestFunct(CTestData param){
    cout<<" TestFunct() : "<<param.GetData()<<endl;
}

void TestFunct_r(const CTestData &param){
    cout<<" TestFunct(&) : "<<param.GetData()<<endl;
}
int main(){
    TestFunct(5);
    TestFunct_r(5);
    return 0;
}

/* 출력 결과
CTestData(int)
 TestFunct() : 5
~CTestData()
CTestData(int)
 TestFunct(&) : 5
~CTestData()
*/

void TestFunct() 함수 : CTestData 클래스가 int자료형에 대한 변환 생성자를 제공함 → param(5) 형태로 임시 객체가 생성됨

void TestFunct_r() 함수 : 컴파일러가 알아서 임시 객체를 생성한 후 임시객체에 대한 참조가 TestFunct_r()로 전달됨 → TestFunct_r(CTestData(5))

\therefore 클래스를 매개변수로 사용하고자 한다면 참조형식으로 사용 + 변환 생성자 앞에 explicit 예약어 사용

→ 효율성을 중시, 사용자 코드에서 헷갈리지 않음

CTestData(int nParam) 앞에 ‘explicit’을 붙여주면 컴파일러가 임시객체를 생성하지 않음!

허용되는 변환

클래스가 변환 생성자를 지원하면 두 형식 사이에 호환성이 생김! → 그러나 반쪽짜리 변환…

클래스형은 int(혹은 다른 것)형으로 변환 불가

형변환 연산자로 변환 가능!!!

#include <iostream>
using namespace std;

class CTestData{
public:
    explicit CTestData(int nParam):m_nData(nParam){
        cout<<"CTestData(int)"<<endl;
    }

    operator int(void){
        return m_nData;
    }

    int GetData() const {
        return m_nData;
    }

    void SetData(int nParam){
        m_nData = nParam;
    }

private :
    int m_nData = 0;
};

int main(){
    CTestData a(10);
    cout<<a.GetData()<<endl;
    cout<<a<<endl; //10번줄 형변환 연산자
    cout<<(int)a<<endl; //C++에서 추천하지X, C언어 스타일
    cout<<static_cast<int>(a)<<endl; //C++스타일 형변환 연산자

    return 0;
}
/* 출력 결과
CTestData(int)
10
10
10
10
*/
  • static_cast 형변환 연사자를 사용하면 제약이 있음 - ‘형변환 가능한 것들'만 해줌. 사용자의 실수를 막아줌.
  • const_cast, static_cast , dynamic_cast, reinterpret_cast 형변환 연산자가 존재함. → 7장!
  • 형변환 연산자에서 explicit 예약어 적용하면 묵시적 변환이 일어나지 않음! (위 코드 10번째줄)


임시 객체와 이동 시맨틱


이름없는 임시 객체

  • 임시객체 : 연산 시에 컴파일러가 알아서 만들어주는데, 데이터형식에 따라서 필요한 용량이 개커질 수 있음…
  • 생성자
    • Default : 클래스이름();
    • 변환 생성자 : 매개변수가 1개, explicit 선언해주어야함. 클래스이름(param);
    • 다중 정의 생성자 : 매개변수 n개
    • 복사 생성자 : 클래스이름(const 클래스이름 &rhs)
  • 이동 생성자 : 클래스이름(const 클래스이름 &&rhs) → r-value reference 복사생성자와 대입연산자에 r-value reference를 조합해서 새로운 생성&대입을 한 것 곧 사라질 r-value에 대하여 복사생성하지 않고 shallow copy 한 다음 원본 포인터가 소멸하는 방식으로 성능을 높여버림!

(스바…C++17 이라서 예제랑 결과가 안맞음…컴파일러 잘 골라서 돌리도록 하자…)

#include <iostream>
using namespace std;

class CTestData{
public :
    CTestData() {
        cout<<"CTestData()"<<endl;
    }

    ~CTestData(){
        cout<<"~CTestData() : "<<endl;
    }

    CTestData(CTestData &rhs)
    : m_nData(rhs.m_nData)
    {
        cout<<"CTestData(CTestData &)"<<endl;
    }

    CTestData(CTestData &&rhs)
    : m_nData(rhs.m_nData)
    {
        cout<<"CTestData(CTestData &&)"<<endl;
    }
    
    CTestData& operator=(const CTestData &) = default;

   int GetData() const {return m_nData;}
   void SetData(int nParam){m_nData = nParam;}

private :
	int m_nData = 0;
};

CTestData TestFunc(int nParam){
    cout<<"***TestFunc() Begin***"<<endl;
    CTestData a;
    a.SetData(nParam);
    cout<<"***TestFunc() End***"<<endl;
    return a;
}

int main(){
    CTestData b;
    cout<<"*****Before*****"<<endl;

    b = TestFunc(20); **//TestFunc의 임시결과를 저장할 때 이동생성자 호출됨**
    cout<<"*****After*****"<<endl;
    CTestData c(b);

    return 0;
}

+) 관련하여 읽어볼 자료
< Effective Modern Cpp > Item17. Special Memeber Function 자동 작성 조건을 숙지하라
-> 복사 생성자, 복사대입연산자, 이동 생성자, 이동대입연산자 에 대해서 나옴.

profile
자율주행 이동체를 배우고 있는 JB입니다.

0개의 댓글