[모던C++입문] 1.8 배열, 포인터, 레퍼런스

    1.8 배열, 포인터, 레퍼런스

    배열

    int x[10]; // 10개의 int 배열
    
    float v[] = {1.0, 2.0, 3.0}, w[] = {7.0, 8.0, 9.0};
    int v2[] = {1.0, 2.0, 3.0};; //C++ 11에서 오류
    
    float A[7][9];      // 7x9 행렬
    float q[3][2][3];   //3x2x3 배열
    
    unsigned int size = sizeof x / sizeof x[0]; //배열의 크기
    
    ifstream ifs("some_array.dat");
    ifs >> size;
    float v3[size];     //컴파일 타임에 크기를 알수 없다

    포인터

    • 포인터는 메모리 주소를 포함하는 변수
    • 포인터를 사용하면 sizeof 트릭을 사용할 수 없다
    int* y = new int[10];   //크기가 10개인 배열을 할당
    
    ifstream ifs("some_array.dat");
    int size;
    ifs >> size;
    float* v = new float[size]; //런타임에 배열의 크기를 정함
    
    delete[] v; //더 이상 필요하지 않을 때 메모리를 해제
    
    int i = 3;
    int* ip2 = &i;
    
    int j = *ip2;   //역참조 (Dereferencing)
    
    //포인터는 무작위 값을 할당하기에 초기화를 해야한다
    //C++11 이전 방식
    int* ip01 = 0;
    int* ip02 = NULL;
    
    //C++11 이후 방식
    int* ip03 = nullptr;
    int* ip04{};
    • 포인터의 가장큰 위험은 메모리 누수이다.
    • 표준 컨테이너를 사용하자
    • 캡슐화 : 메모리는 개체 생성 시 할당하고 파괴시 해제해야함
      • RAII(Resource Acquisition Is Initialization)
    • 스마트 포인터를 사용하자

    스마트 포인터

    unique_ptr

    • 고유 소유권(Unique Ownership)을 가지는 포인터
    • 원시 포인터와 달리 포인터가 만료되면 메모리가 자동으로 해제
    • 원시 포인터에 비해 시간과 메모리에 대한 오버헤드가 없다
    unique_ptr<double> dp{new double}; //일반적인 할당
    
    double d;
    unique_ptr<double> dd{&d};  //오류 할당하지 않은 주소를 할당
    
    double* raw_dp = dp.get();  //포인터 데이터를 얻기 위해서는 get함수 사용
    
    unique_ptr<double> dp2{dp}; //오류 : 다른 unique_ptr에 할당 불가
    dp2 = dp;                   //상동
    
    unique_ptr<double> dp2{move(dp)}, dp3;  //unique_ptr는 이동만 가능
    dp3 = move(dp2);                        //상동

    shared_ptr

    • 여러 포인터에서 공통으로 사용하는 메모리를 관리
    • shared_ptr이 더 이상 데이터를 참조하지 않으면 즉시 메모리를 자동 해제
    • unique_tr과 달리 원하는 만큼 복사할 수 있다.
    • shard_ptr에는 메모리와 실행시간에 약간의 오버헤드가 존재
    shard_ptr<double> f()
    {
        shard_ptr<double> p1{new double};
        shard_ptr<double> p2{new double}, p3 = p2;
        cout << "p3.use_count() = " << p3.use_count() << endl;
        return p3;
        //함수를 빠져나갈때 p1은 메모리를 해제한다
    }
    
    int main()
    {
        shard_ptr<double> p = f();
        cout << "p.use_count() = " << p.use_count() << endl;
    }
    
    //출력 결과
    p3.use_count() = 2;
    p.use_count() = 1;
    
    //가능하다면 make_shared를 사용해서 shared_ptr를 만들자
    shared_ptr<double> p1 = make_shard<double();

    weak_ptr

    • shared_ptr에서 발생할수 있는 문제인 순환참조(Cycle reference)를 중단할 수 있음

    레퍼런스

    int  i = 5;
    int& j = i;
    j = 4;          //i도 4가 된다
    cout << "i = " << i << endl;    //i와 j는 항상 같은 값을 가짐

    포인터와 레퍼런스 비교

    특징 포인터 레퍼런스
    정의된 위치 참조 x o
    초기화 필수 x o
    메모리 누수 방지 x o
    개체와 같은 표기법 x o
    메모리 관리 o x
    주소 계산 o x
    컨테이너 만들기 o x

    오래된 데이터를 참조하지 말자

    //이러지 말자
    double& square_ref(double d)
    {
        double s = d*d;
        return s;
        // 지역변수를 참조하면 안된다 부실 레퍼런스(stale reference)
    }
    
    double* square_ptr(double d)
    {
        double s = d*d;
        return &s;
        // 스코프를 벗어난 지역주소를 가지면 댕글링 포인터(dangling pointer)
    }

    배열용 컨테이너

    표준 벡터

    #include <vector>
    
    int main()
    {
        std::vector<float> v(3), w(3);
        v[0] = 1; v[1] = 2; v[2] = 3;
        w[0] = 7; w[1] = 8; w[2] = 9;
        //위 방식보단 아래 방식을 사용하자
    
        std::vector<float> v = {1,2,3}, w = {7,8,9};
    
         int vSize = v.size();      //배열의 사이즈를 구할 수 있음
    }

    valarray

    • 요소별 연산을 사용하는 1차원 배열
    #include <valarray>
    
    int main()
    {
        std::valarray<float> v = {1,2,3}, w = {7, 8, 9} , s = v + 2.0f * w;
        v = sin(s);
    }
    반응형

    댓글

    Designed by JB FACTORY