-
c++ 참조자, 생성자, 복사생성자카테고리 없음 2022. 1. 4. 21:39
요새 자바 타입(?)의 언어만 하다가 오랜만에 c++을 했더니 헷갈리는 것들이 많아서
c++에서 헷갈리는 것들을 정리해보았다.
1. 참조자
참조자에 대해 너무 잘 설명해 놓은 글이 있어 링크로 대체한다.
참조자는 포인터 대신 사용할 수 있다.
배열이나 부피가 큰 클래스를 함수의 매개변수로 전달해야 하는 경우,
객체의 본체를 복사해서 전달하면 메모리 면에서 비효율적이다.
참조자를 사용하면 마치 포인터처럼 주소만 전달해서 메모리 효율을 높일 수 있고,
전달받은 참조자를 사용할때는 &를 사용할 필요 없이 마치 본체인 양 쓰면 된다.
참조자가 무엇인지에 대한 설명은 생략하고 둘의 차이점만 간략하게 요약한 것:
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; }