ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • c++ 참조자, 생성자, 복사생성자
    카테고리 없음 2022. 1. 4. 21:39

    요새 자바 타입(?)의 언어만 하다가 오랜만에 c++을 했더니 헷갈리는 것들이 많아서

    c++에서 헷갈리는 것들을 정리해보았다.

     

    1. 참조자

     

    참조자에 대해 너무 잘 설명해 놓은 글이 있어 링크로 대체한다.

    https://modoocode.com/141

     

    참조자는 포인터 대신 사용할 수 있다.

    배열이나 부피가 큰 클래스를 함수의 매개변수로 전달해야 하는 경우,

    객체의 본체를 복사해서 전달하면 메모리 면에서 비효율적이다.

    참조자를 사용하면 마치 포인터처럼 주소만 전달해서 메모리 효율을 높일 수 있고,

    전달받은 참조자를 사용할때는 &를 사용할 필요 없이 마치 본체인 양 쓰면 된다.

     

    참조자가 무엇인지에 대한 설명은 생략하고 둘의 차이점만 간략하게 요약한 것:

    Peter의 우아한 프로그래밍 :: [C++] 포인터(Pointer)와 레퍼런스(Reference : 참조자)의 차이 (tistory.com)

     

     

    2. 객체 생성하기

     

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Person {
        string name;
        int age;
    
    public:
        Person() : Person("", 0) {} // 디폴트 생성자
        Person(string name, int age) {
            this->name = name;
            this->age = age;
        }
    
        void print_name() {
            cout << "name: " << name << endl;
        }
    };
    
    int main() {
        Person p1; // 암시적 생성
        Person p2 = Person(); // 명시적 생성
        Person p3("Steve", 30); // 암시적 생성
        Person p4 = Person("Jenny", 10); // 명시적 생성
        Person* p5 = new Person(); // 동적 생성
        // Person p6(); 이렇게 하면 안 된다.
    
        delete p5; // 동적으로 생성한 경우는 메모리 해제 필요
    
        return 0;
    }

     

    디폴트 생성자를 암시적으로 호출하고 싶을 때는 괄호를 사용하면 안된다.

    http://tcpschool.com/cpp/cpp_conDestructor_defaultConstructor

     

    생성할때는 전혀 오류가 발생하지 않다가, 나중에 사용하려고 할 때 오류가 발생한다.

    int main() {
        Person p6();
        // p6.print_name();
        // 에러 발생: 식에 클래스 형식이 있어야 하는데 "Person(*)()" 형식이 있음.
    
        return 0;
    }

    p6.name 부분에 빨간 줄이 생기면서 "식에 클래스 형식이 있어야 하는데 "Person (*)()" 형식이 있음" 이라는 E0153 에러 발생한다. (비주얼 스튜디오 2019 기준)

     

    복사 생성자를 설명한 글:

    [C++] 복사 생성자, 복사 대입 연산자 (+깊은 복사) (tistory.com)

     

    복사 생성자는 만들려고 하는 객체와 동일한 클래스의 객체를 매개변수로 받아 멤버변수의 값이 동일한 객체를 생성한다.

    복사 생성자는 직접 생성하지 않아도 컴파일러가 알아서 생성해준다.

    그래서 예시와 아래와 같이 복사생성자를 만들어주지 않아도 사용할 수 있다.

     

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Person {
        string name;
        int age;
    
    public:
        Person() : Person("", 0) {} // 디폴트 생성자
        Person(string name, int age) {
            this->name = name;
            this->age = age;
        }
    
        void print_name() {
            cout << "name: " << name << endl;
        }
    
        void change_name(string name) {
            this->name = name;
        }
    };
    
    int main() {
        Person p3("Steve", 30);
        Person p4 = Person("Jenny", 10);
        
        Person p7 = p3; // 복사 생성. name이 "Steve"이고 age가 30인 Person 생성.
        Person p8(p4); // 복사 생성. name이 "Jenny"이고 age가 10인 Person 생성.
    
        // p3의 이름은 그대로 "Steve"이고 p7의 이름만 "Sam"으로 바뀐다.
        p7.change_name("Sam");
    
        return 0;
    }

     

    기본으로 생성해주는 복사 생성자와 다른 기능을 하도록 만들고 싶다면 직접 복사 생성자를 하나 만들어주면 된다.

    아래와 같은 매개변수 형태로 만들어주면 된다.

    class Person {
        string name;
        int age;
    
    public:
        Person(const Person& rhs) { // 복사 생성자
            // 원하는 내용 추가
        }
        // 생략
    };

     

    멤버 변수 중에 포인터를 사용하는 멤버 변수를 포함해서 예제를 만들어보았다.

    #include <string>
    #include <stdio.h>
    
    using namespace std;
    
    class Numbers
    {
        int* numbers;
        int numberOfNumbers;
    
    public:
        Numbers() {
            numbers = nullptr;
            numberOfNumbers = 0;
        }
        ~Numbers() {
            if (numbers != nullptr)
                delete numbers;
        }
        void SetNumbers(int* numbers, int numberOfNumbers) {
            if (this->numbers != nullptr) delete this->numbers;
    
            int* tmp = new int[numberOfNumbers];
    
            for (int i = 0; i < numberOfNumbers; ++i)
            {
                tmp[i] = numbers[i];
            }
    
            this->numbers = tmp;
            this->numberOfNumbers = numberOfNumbers;
        }
        void ChangeNumber(int index, int value) {
            if (numberOfNumbers <= index) return;
            numbers[index] = value;
        }
        void PrintNumbers() {
            if (numbers == nullptr) return;
            for (int i = 0; i < numberOfNumbers; ++i) {
                printf("%d ", numbers[i]);
            }
            printf("\n");
        }
    };
    
    int main() {
        Numbers n1;
    
        int numbers[3];
    
        numbers[0] = 1;
        numbers[1] = 4;
        numbers[2] = 52;
    
        n1.SetNumbers(numbers, 3);
        n1.PrintNumbers(); // 1 4 52
    
        Numbers n2(n1);
    
        n2.ChangeNumber(1, 8);
        n1.PrintNumbers(); // 1 8 52
        n2.PrintNumbers(); // 1 8 52
    
        return 0;
    }

     

    주석의 PrintNumbers 결과를 보면 알 수 있듯이, n2가 n1을 얕은 복사 해서 메모리를 공유하게 되었다.

    n2의 숫자를 바꾸면 n1의 숫자도 같이 변한다.

    그리고, 소멸자가 불릴 때 이미 해제한 동일한 메모리에 한 번 더 delete가 시도되기 때문에 제대로 끝나지도 못한다.

    컴파일러가 자동으로 만들어주는 복사 생성자가 아니라, 직접 깊은 복사를 수행하는 복사 생성자를 만들어주면 해결 가능하다.

     

    #include <string>
    #include <stdio.h>
    
    using namespace std;
    
    class Numbers
    {
        int* numbers;
        int numberOfNumbers;
    
    public:
        Numbers() {
            numbers = nullptr;
            numberOfNumbers = 0;
        }
        ~Numbers() {
            if (numbers != nullptr)
                delete numbers;
        }
        Numbers(const Numbers& rhs) { // 깊은 복사를 하는 복사 생성자
            SetNumbers(rhs.numbers, rhs.numberOfNumbers);
        }
        void SetNumbers(int* numbers, int numberOfNumbers) {
            if (this->numbers != nullptr) delete this->numbers;
    
            int* tmp = new int[numberOfNumbers];
    
            for (int i = 0; i < numberOfNumbers; ++i)
            {
                tmp[i] = numbers[i];
            }
    
            this->numbers = tmp;
            this->numberOfNumbers = numberOfNumbers;
        }
        void ChangeNumber(int index, int value) {
            if (numberOfNumbers <= index) return;
            numbers[index] = value;
        }
        void PrintNumbers() {
            if (numbers == nullptr) return;
            for (int i = 0; i < numberOfNumbers; ++i) {
                printf("%d ", numbers[i]);
            }
            printf("\n");
        }
    };
    
    int main() {
        Numbers n1;
    
        int numbers[3];
    
        numbers[0] = 1;
        numbers[1] = 4;
        numbers[2] = 52;
    
        n1.SetNumbers(numbers, 3);
        n1.PrintNumbers(); // 1 4 52
    
        Numbers n2(n1);
    
        n2.ChangeNumber(1, 8);
        n1.PrintNumbers(); // 1 4 52
        n2.PrintNumbers(); // 1 8 52
    
        return 0;
    }

     

     

    댓글

Designed by Tistory.