장르: 애초에 역경을 딛고 이룩하는 숭고한 사랑이란 없다. 그 역경 자체가 사랑이다.
프로그램 특징: 그 곳에서 살아남는 사랑이 어떤 모습으로 걸어오는지 기다려 보고 싶다.
-
[JAVA] 백준할 때마다 헷갈리는 자바 기본 / 자료형과 연산자, 출력 문법 정리0415분보호되어 있는 글입니다.
-
복사 생성자1113분복사생성자 copy constructor생략 시 컴파일러가 자동 생성해주지만 클래스 내부에서 메모리 동적 할당 및 해제하고 이를 멤버 포인터로 관리하는 경우 직접 선언해야 한다.이를 이해하기 위해선 Pass by value의 선행 이해가 필요하다.다른 함수의 매개변수로 사용되는 경우 Call by value 로 전달된다. 호출 함수 스택에 따로 메모리를 할당해 객체의 복사된 값을 전달되는 형태인데 이를 복사 생성자라고 생각하면 된다.Myclass(const Myclass &rhs)// :m_data(rhs.m_data) { this -> m_data = rhs.m_data; cout a 를 복사의 원본으로 rhs가 a를 참조하는 형태 원본을 복사하는 형태기 때문에 멤버 m_data 두 ..
-
[Template] Class template1018분함수 템플릿과 클래스 템플릿템플릿을 함수에서 사용할 수 있고 클래스에서 사용할 수 있다. 둘 다 틀 로 사용하는 것인데 함수 수준에서 클래스 수준으로 확대된 것만 다르다.템플릿템플릿은 overloading에서 함수를 특정 사용 매개변수, 반환 형식에 따라 여러 번 작성해줘야 한다는 불편함을 위해 효율적인 메모리와 시간 관리를 위해 나온 개념이다.// function template template tem function(tem arg){ std::cout class SortedType {private: ItemType data[MAX_ITEMS]; int length;public: SortedType(); void insertItem(ItemType value); bool f..
-
[Class] Method1016분Method a.k.a. Interface Function a.k.a. Member FunctionMethod default formstatic return_type class name :: function (arguments) const;this pointer현재 설계 중인 제품의 전화 번호는 미래에 결정된다. 그런데 제작자는 아직 결정되지 않은 전화 번호를 이용해야 하는 코드를 작성해야 할 수 있다.현재 시점에서 미래의 전화 번호에 접근하는 방법이 this pointer 입니다.int main(){ USERDATA user = { , , print}; user.Print(&user); // cpp user.Print(); return 0;}클래스 객체(인스턴스) user가 ..
-
[Object-oriented programming] Class1016분C의 절차지향에서 객체 지향으로 인식을 전환해야 합니다. 인식 전환의 방법을 나를 남보듯, 손님 대하듯이 - 제작자 마인드와 사용자 마인드를 분리하는 것입니다. (코드를 남보기 쉽게 짠다는 뜻) 왜냐면 6개월 후의 나는 코드를 이해할 수 없기 때문입니다.클래스란 C 구조체에서 확정된 변수, 객체, 함수를 포함한 하나의 틀 이다. C의 구조체는 함수를 포함할 수 없지만 C++는 가능하다.- 객체란 변수들과 관련된 메서드들이 모여서 만든 하나의 꾸러미다.- 메서드란 함수 형태로 클래스의 실제 동작과 상태를 구현하는 핵심 요소다.| 메서드랑 함수는 뭐가 다르고, 변수랑 함수가 모인 객체는 클래스랑 뭐가 다른가요? 왜 용어를 이렇게 여러 개 사용하시나요?객체 지향의 패러다임을 설명하기 위해 세분화된 것입니다.•..
-
[Polymorphism] Overloading1015분cpp 에서는 함수 원형이 달라지면 이름이 같아도 다른 함수가 된다. c와 중요한 차이점이다. # Overloading함수 원형(반환 형식(type), 호출 규칙, 함수 이름, Argument 구성) 중 함수 이름을 제외하고 다르게 변경할 수 있다. 그러나 반환 형식이나 호출 규칙만 다른 경우는 문법에 맞지 않다. 즉, 함수를 이름은 같고 내용은 다르게 재정의할 수 있다. - int Add(int a, int b) {} - double Add(double a, double b) {}- int Add(int a, int b, int c) {} 호출자 관점에서 호출되는 함수는 컴파일러에 의해 자동으로 결정된다. -> 모호성void TestFunc(int a){ std::cout 위 코드는 컴파일할 수 있는..
복사생성자 copy constructor
생략 시 컴파일러가 자동 생성해주지만 클래스 내부에서 메모리 동적 할당 및 해제하고 이를 멤버 포인터로 관리하는 경우 직접 선언해야 한다.
이를 이해하기 위해선 Pass by value의 선행 이해가 필요하다.
다른 함수의 매개변수로 사용되는 경우 Call by value 로 전달된다. 호출 함수 스택에 따로 메모리를 할당해 객체의 복사된 값을 전달되는 형태인데 이를 복사 생성자라고 생각하면 된다.
Myclass(const Myclass &rhs)
// :m_data(rhs.m_data)
{
this -> m_data = rhs.m_data;
cout << "Myclass(const Myclass &)" << endl;
}
int main(){
// 기본 생성자
Myclass a;
a.Setdata(10);
// 복사 생성자
Myclass b(a);
}
a 를 복사의 원본으로 rhs가 a를 참조하는 형태 원본을 복사하는 형태기 때문에 멤버 m_data 두 개가 한꺼번에 나타난다.
이를 해결하기 위해 this -> m_data 를 표현해주거나 생성자 초기화 m_data(rhs.m_data)를 이용한다.
복사 생성자 호출
- 명시적 호출
Myclass a; - 함수 형태 호출
Myclass b(a);1. 클래스가 매개변수로 사용 2. 클래스가 반환형식으로 사용 - 반환 형식으로 사용되는 경우 '이름 없는 임시 객체' 를 생성한다. 느껴지는 대로 골치 아픈 문제다.
- 함수 형태 호출에서도 두 가지 사용 형식이 존재한다.
우리에게 더 익숙한 복사 생성자 호출 상황은 아래와 같다.
Myclass b;
b = a;
복사 생성자를 매개 변수로 사용
void TestFunc(Myclass param) {}
TestFunc param의 호출은 Myclass 인스턴스 원본 a를 두고 복사본을 생성한다. (함수 내부에서 쓸 용)
그렇게 되면 쓸데없이 클래스 객체가 두 개가 된다. 하나만 있어도 되는데 두 개로 처리한 것도 문제지만 함수 호출 자체가 성능 문제로 직결되기 때문에 수정이 필요하다. 복사 생성자를 삭제할까?
Myclass(const Myclass &rhs) = delete;
가능하지만.. 함수 호출 시 삭제된 함수를 참조하려 한다는 컴파일러의 오류 메세지를 만나게 된다.
*rhs: right hand side 의 약자로 그냥 많이 쓴단다. 뭔지 잘 모르겠다.
Call by reference
void TestFunc(Myclass ¶m){}
복사 생성자를 이용하지 않고도 원본을 argument로 전달해줄 수 있게 되었다.
사용자는 TestFunc(a); 로 더 높은 성능을 이용할 수 있다.
만약 void TestFunc(Myclass *param) 이었다면 사용자는 TestFunc(&a);를 파라미터로 주었어야 할 것이다.
동일한 성능을 나타내지만 포인터를 파라미터로 받을 시 댕글링 포인터(포인팅하는 쪽이 없어지는 경우)나 의존 관계 분석 불가로 인한 코드 최적화 실패 등의 단점이 있기 때문에 참조자&로 대체할 수 있는 포인터는 무조건 대체하는 것이 좋다.
사용자가 어떠한 값을 입력해야 하고, 그 값이 함수 내에서 변형될 수 있는 지를 알 수 있는 것은 프로그램 사용에 있어서 중요한 문제다.
void TestFunc(const Myclass ¶m){}
이었다고 치자.
사용자가 TestFunc 함수 내부에서 클래스 인스터스 param.setdata(10); 등의 명령어를 입력했을 때의 결과를 알 수 있을까?
사용자는 모른다.
const 참조라 원형값을 변경할 수 없음에도 뻘짓을 하고 있을지도 모른다.
근데 원형을 파라미터로 받는 경우에는 const를 꼭 사용하자. 잠깐 편하자고 ~~ 코드 뜯어볼 일을 만들지 말자.
그래서 이제 우리가 할 것은 무엇이냐면~
동적 생성한 인스턴스의 값을 관리하는 방법에 대해 알아볼 것이다.
깊은 복사와 얕은 복사
얕은 복사: 메모리 주소 가져오기
깊은 복사: 메모리 주소의 값 가져오기
int *ptr = new int;
*ptr = 10;
int *ptr2 = new int;
ptr2 = ptr;
// 지금 ptr2 는 ptr이 값으로 가진 new int (10)의 주소를 받았다.
delete ptr;
delete ptr2;
무슨 문제가 일어날까? ptr2는 이미 delete ptr에서 자신이 가리키고 있는 메모리 주소의 집주인이 사라졌으므로 할당 해제된 상태다.
여기서 한 번 더 할당 해제를 시도하면 오류가 난다.
ptr -> 10
ptr2 ---^ [new int] (접근 불가)
*ptr2 = *ptr;
로 수정하면 ptr2 는 자신이 동적 할당한 int 메모리에 10을 넣어서 그걸 가리키고 있을 것이다.
ptr -> 10
ptr2 -> 10
포인터가 존재할 때 얕은 복사가 문제되는 상황
class Myclass
{
public:
Myclass(int nParam)
{
m_dataPtr = new int;
*m_dataPtr = nParam;
}
int getdata(){
if (m_dataptr != nullptr){
return *m_dataptr;
}
return 0;
}
private:
int *m_dataptr = nullptr;
};
int main(){
Myclass a;
Myclass b(a);
cout << a.getdata() << endl;
cout << b.getdata() << endl;
return 0;
}
문제는 어디서 나타난걸까
Myclass b(a); 를 수행하면 깊은 복사 수행이 아니기 때문에 컴파일러가 자동으로 Myclass b(a);에서
Myclass(const Myclass &rhs){
m_dataptr = rhs.m_dataptr;
}
를 실행한다.
여기서 메모리 해제를 시도했다면
// 객체 소멸 시점에 동적 할당 메모리 삭제
~Myclass() { delete m_dataptr; }
( 소멸자는 블록 {} 이 끝나면 실행된다 )
복사 생성자인 b는 원본 데이터의 m_dataptr;이 삭제된 상태에서 또 소멸자를 부르기 때문에 앞서서 봤던 해제된 메모리를 다시 해제하려고 시도한 오류가 나타난다.
그럼 또 어떡하라공
복사 생성자 정의하고 깊은 복사
Myclass(const Mydata &rhs)
{
// 디버그용
cout << "복사생성자 호출" << endl;
// 메모리 할당
m_dataptr = new int;
*m_dataptr = *rhs.m_dataptr;
}
인스턴스 복사 후 소멸자가 나와도 복사 생성자는 자신이 할당한 메모리를 가리키고 있기 때문에 더 이상 해제된 메모리를 해제하는 오류는 나타나지 않는다.
저자 왈, 이 부분 객체지향 배우면 시험 범위라니까 모두들 열심히 이해해봅시다.
추가로 ...
앞서서, 우리에게 익숙한 방식 b = a; 로 단순 대입(pass by value)한 결과에는 대입 연산자 동작 방식을 새로 정의해주어야 한다.
Myclass& operator = (const Myclass &rhs)
{
*m_dataptr = *rhs.m_dataptr;
// 객체 자신에 대한 참조 반환
return *this;
}
// b = a; 대신 아래와 같이 이용할 수도 있다.
b.operator=(b);
연산자 = 의 작동 방식을 정의해준 코드로 .. 어렵다
a의 모든 값을 새로 정의한 복사 생성자 new로 넣기 위해 깊은 복사를 정의해준건가?
b = a; 면 a의 m_dataptr 값이 = 에서 *m_dataptr = *rhs.m_dataptr;로 복사되어 b의 m_dataptr이 된건가
그럼 *rhs.m_dataptr; 를 반환하면 안되나?
... 어물쩡 넘기기 ㅎ
전체 코드
class Myclass
{
public:
Myclass(int nParam)
{
m_dataptr = new int;
*m_dataptr = nParam;
}
Myclass(const Myclass &rhs)
{
cout << "Myclass const &rhs" << endl;
m_dataptr = new int;
*m_dataptr = *rhs.m_dataptr;
}
~Myclass()
{
delete m_dataptr;
}
Myclass& operator=(const Myclass &rhs)
{
*m_dataptr = *rhs.m_dataptr;
return *this;
}
int getdata()
{
if (m_dataptr != nullptr)
return *m_dataptr;
return 0;
}
private:
int *m_dataptr = nullptr;
};
int main()
{
Myclass a(10);
Myclass b(20);
a = b;
cout << a.getdata() << endl;
return 0;
}
답은?
20
- 이 글은 최호성 저자의 이것이 C++이다 를 참고하여 작성되었습니다.
'Programming' 카테고리의 다른 글
[BOJ 10552][JAVA] 박싱/언박싱 (0) | 2025.04.15 |
---|---|
[JAVA] 백준할 때마다 헷갈리는 자바 기본 / 자료형과 연산자, 출력 문법 정리 (0) | 2025.04.15 |
[Template] Class template (0) | 2024.10.18 |
[Class] Method (2) | 2024.10.16 |
[Object-oriented programming] Class (1) | 2024.10.16 |