[모던C++입문] 2.3 생성자 및 할당 연산자

    2.3 생성자 및 할당 연산자

    생성자

    class complex
    {
    public:
        //디폴트 생성자
        complex(double rnew, double inew)
            : r(), 1()      //컴파일러가 생성
        {
            r = rnew, i = inew;
        }
    
        //멤버 초기화 리스트(Initialization List)를 이용한 생성자
        //초기화 순서와 정의 순서가 일치하지 않으면 컴파일러가 경고를 출력
        complex(double rnew, double inew) : r(rnew), i(inew) {}
    
    private:
        double r, i;
    }
    
    //생성자를 통한 값 설정
    complex c1(2.0, 3.0);
    
    
    class matrix_type
    {
    public:
        matrix_type(int nrows, int ncols) {}
        //후략
    };
    
    class solver
    {
    public:
        solver(int nrows, int ncols)
        // : A()       오류 : 존재하지 않는 디폴트 생성자 호출
        {
            A(nrows, ncols);    //오류 : 생성자 안에서 다른 생성자를 호출할 수 없음
            //A.operator()(nrows, ncols); 로 해석
        }
    private:
        matrix_type A;
    }
    class complex
    {
        complex(double r, double i) : r(r), i(i) {}
        complex(double r) : r(r), i(0) {}
        complex() : r(0), i(0) {}
    
        //위 3가지 생성자는 아래생성자로 대체 가능
        complex(double r = 0, double i = 0) : r(r), i(i) {}
    }
    
    complex z1;     //디폴트 생성
    complex z2();   //디폴트 생성이 아니라 complex를 반환하는 함수 선언
    complex z3(4);  //z3(4.0, 0.0)
    complex z4 = 4; //z4(4.0, 0.0)
    • C++에는 다음 3가지 특수 생성자를 갖는다
      • 디폴트 생성자
      • 복사 생성자 (Copy Constructor)
      • 이동 생성자 (Move Constructor) C++11

    디폴트 생성자

    • 인수가 없는 생성자 또는 모든 인수가 기본값을 갖는 생성자
    • 가능하면 디폴트 생성자를 정의하자

    복사 생성자

    class complex
    {
    public:
        complex(const complex& c) : i(c.i), r(c.r) {}
    
        //복사 생성자의 인수는 값에의한 전달을 할수 없다 (무한루프에 빠질수 있음)
        //complex(complex c) : i(c.i), r(c.r) {}    //빌드 오류
        //...
    }
    
    int main()
    {
        complex z1(3.0, 2.0),
        z2(z1),     //복사
        z3{z1};     //C++11 축소하지 않음
    }
    • 복사생성자가 필요한 경우는 클래스에 포인터가 포함되어있는 경우이다
    class vector
    {
    public:
        vector(const vector& v)
            : size(v.size), data(new double[size])
        {
            for(int i = 0; i < size; i++)
                data[i] = v.data[i];
        }
        ~vector() { delete[] data; }
    
    private:
        int size;
        double* data;
    
        //data의 타입은 원시 포인터보다는 unique_ptr을 사용하자
        std::unique_ptr<double[]> data;
    }

    변환과 명시적 생성자

    complex c1{3.0};        //C++11이상
    complex c1(3.0);        //모든 표준
    complex c1 = 3.0;
    
    double inline complex_abs(complex c)
    {
        return sqrt(c.r * c.r * c.i * c.i);
    }
    
    //complex_abs에 double 타입을 허용하는 함수 오버로드가 없지만
    //complex에는 double타입을 허용하는 생성자가 있으므로
    //double리터럴에서 complex값을 암시적으로 생성한다
    cout << "|7| = " << complex_abs(7.0) << endl;
    
    //다음과 같이 생성자를 explicit로 선언하면 암시적 생성을 비활성화 할 수 있다
    explicit complex(double r = 0.0, double i = 0.0) : r(r) i(i) {}
    
    //그러면 이제 명시적으로 complex를 생성해야한다
    cout << "|7| = " << complex_abs(complex{7.0}) << endl;

    위임 (C++11)

    • c++11에서는 생성자 위임(Delegating constructor)을 제공
    • 생성자에서 다른 생성자를 호출하는 기능
    class complex
    {
    public:
        complex(double r, double i) : r{r}, i{i} {}
        complex(double r) : complex{r, 0.0} {}
        complex() : complex{0.0, 0.0} {}
        //...
    }

    멤버의 기본값 (C++11)

    class complex
    {
    public:
        complex(double r, double i) : r{r}, i{i} {}
        complex(double r) : r{r} {}
        complex() {}
    private:
        //다음과 같이 선언과 동시에 값을 입력하여 기본값을 설정
        double r = 0.0, i = 0.0;
    }

    할당

    • 개체 타입의 값을 할당하는 연산자를 복사 할당 연산자(copy assignment operator)
    complex& operator=(const complex& src)
    {
        r = src.r, i = src.i;
        return *this;
    }
    • 복사 할당 연산자는 복사 생성자와 유사하다
    vector& operator=(const vector& src)
    {
        if(this == &src)    //자기자신에게 할당하는 경우
            return *this;
        assert(my_size == src.my_size); //크기가 같은지 검사
        for( int i = 0; i < mysize; i++)
            data[i] = src.data[i];
        return *this;
    }

    초기화 리스트(C++11)

    float v[] = {1.0, 2.0, 3.0};    //일반적인 배열 초기화
    
    //모든 클래스는 같은타입의 값 목록으로 초기화 가능
    vector v = {1.0, 2.0, 3.0};
    vector v{1.0, 2.0, 3.0};

    유니폼 초기화(C++11)

    • { } 중괄호는 변수 초기화를 위한 보편적인 표기법이다.
      • 초기화 리스트 생성자
      • 다른 생성자
      • 직접 멤버 설정
    • 모든 변수가 public이고 클래스에 사용자 정의의 생성자가 없는 경우 배열과 클래스에서만 가능하다 (집합 초기화 Aggregate Initialization)
    • 집합 초기화 보다는 생성자를 사용하는 방식을 선호
    struct sloppy_complex
    {
        double d, r;
    }
    
    sloppy_complex z1{3.66, 2.33},
                    z2 = {0, 1};
    
    //유니폼 초기화의 인수로 초기화 리스트를 사용하고자 한다면 2중의 중괄호가 필요
    vector v1 = {{1.0, 2.0, 3.0}},
            v2{{3, 4, 5}};
    
    //C++11에서는 중괄호 생략을 제공
    vector v1 = {1.0, 2.0, 3.0},
            v2{3, 4, 5};
    
    //그러나 다음과 같은 현상이 발생할수 있다.
    vector_complex v1d = {{2}};             //[(2,0)]
    vector_complex v2d = {{2,3}};           //[(2,3)]
    vector_complex v3d = {{2,3,4}};         //[(2,0),(3,0),(4,0)]
    //3개의 인수를 같는 complex 생성자가 없기에 중괄호가 생략된 complex목록으로 인식
    • 유니폼 초기화는 유용하지만 특수한 사례에서는 주의해야한다

    이동 문법

    • Lvalue : 표현식 이후에도 없어지지 않고 지속되는 객체
    • Rvalue : 표현식 이후에는 더이상 존재하지 않는 임시적인 값

    이동 생성자

    • 이동 생성자는 원본에서 데이터를 훔처 빈 상태로 둔다
    class vector
    {
        vector(vector&& v)
            : my_size(v.my_size), data(v.data)
        {
            v.data = 0; //or nullptr(c+11)
            v.mysize = 0;
        }
    }

    이동 할당 연산자

    class vector
    {
        vector& operator=(vector&& src)
        {
            assert(my_size == 0 || my_size == src.mysize);
            std::swap(data, src.data);
            return *this;
        }
    }

    복사 생략 (Copy Elision)

    • 복사 작업 대상 주소에 즉시 저장하도록 한다
    inline vector ones(int n)
    {
        vector v(n);
        // 초기화 생략
        return v;
    }
    
    vector w(ones(7));
    //컴파일러는 v를 생성하지 않고 w를 생성하여 w주소에서 연산을 수행
    //복사(또는 이동) 생성자를 호출하지 않음

    이동 문법이 필요할 때(C++11)

    • 이동 생성자를 확실히 사용하려면 std::move 함수를 사용하자
    • Lvalue를 Rvalue로 변환하여 이동 가능하게 한다
    반응형

    댓글

    Designed by JB FACTORY