C++ 이론(4)

Hi Beck·2023년 7월 5일

TIL-Language(C & C++)

목록 보기
30/34

복사생성자는 언제 호출되는가?

1.복사생성자를 쓸 때 크래시 일어나는 상황때문에 객체를 객체로 호출하는 상황에서 두 객체가 같은 동적할당받은 주소를 가리켜 소멸할때 문제가 됐었는데 그걸 받을 객체에 동적할당만 새로 해줘서 박스만 받고 그 안의 값은 넣을 객체로부터의 값만 복사해줬는데
그때 쓰는 함수의 매개변수에 const가 들어가는 이유는?

-> 우리가 rhs로 넣을 객체를 참조하는데 이 넣을 객체는 변질시키고자하는 의도가 없기에 레퍼런스 변수(별명붙여서)로 가져오고 거기에 const 상수화를 시켜서 변하지않게 해주는것이다.

2.얕은복사(shallow copy)와 깊은복사(deep copy)

면접 질문 베스트!!!
얕은복사와 깊은복사의 차이점을 설명하시오.
복사생성자에 대해 설명해보세요.

ㄱ.얕은복사(shallow copy)
객체를 가지고와서 호출할때 복사생성자(동적할당을 정의한)에 의해서 포인터변수로 똑같은 주소값을 가져와버려서 똑같은 애를 가리키면서 문제가 됨.이렇게 문제가 된 상황을 '얕은복사'했다라고 한다.

  • 얕은복사는 무조건 잘못된것이 아니라 복사생성자에 한해서는 잘못된 방법이다.개념일뿐
  • 얕은 복사는 이동생성자란것에 쓰인다.
    복사생성자가 동적할당을 해서 성능(시간)이 좀 안좋다.(운영체제가 빌려주기 전 가능한지 검사하고 빌려주는데 시간이 소요됨) 그렇기때문에 성능때문에 얕은복사를 쓸 때도 있는데 그게 바로 이동생성자이다.(현업에서 어려워서 사용도가 낮다보니 이건 현직때 공부하기)

ㄴ.깊은복사(deep copy)
그래서 가리키는 공간을 새로 만들고 넣을 객체의 값만 복사해서 넣어주는 코드를 추가로 짜주면 해결된다.이것을 '깊은복사'라고 한다.

-> 복사생성자를 정의할 때는 꼭 깊은복사로 정의하여야한다.

3.복사생성자는 언제 불리는가?(=호출시점)
복사생성자는 복사해서 생성될 때 쓰이며,
복사 생성자의 호출시점은 크게 세가지로 구분된다.

1) 기존에 생성된 객체를 이용해 새로운 객체를 초기화 하는 경우
2) Call-by-value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우
3) 객체를 반환하되, 참조형으로 반환하지 않은 경우

1)

ex) person man1("Lee dong woo", 29);
person man2 = man1;

OR CMydata b(a)

2) Call-by-value 방식
함수에다가 인자로 객체를 넘겨줄때.근데 그 객체를 넘겨주는 형태가 포인터나 레퍼런스가 아닌 그냥 복사하는 형태일때

ex)
위에 여러 생성자들이 있음

void SimpleFuncObj(SoSimple ob) //객체를 인자로 받는 함수 정의문(소심플은 클래스)
{ //레퍼런스변수나 포인터변수가 없다
ob.showData();
}

int main(void)
{
SoSimple Obj(7); //메인함수에 Obj라는 객체 선언(지역객체)
cout<<"함수 호출 전"<<endl;
SimpleFuncObj(Obj); //정의한 함수를 호출하면서 위의 Obj 객체를 넘김
cout<<"함수 호출 후"<<endl;
return 0;
}

함수에서 Obj를 호출할때 Obj를 그대로 가져가는게 아니라 SimpleFuncObj 함수 내부의 ob라는 지역객체를 생성한다.(SoSimple ob는 void SimpleFuncObj 내부에서만 사는 지역변수기 때문에) Ob를 생성하는데 Obj를 가지고 생성한다.

함수에 있는 매개변수는 함수에 정의되어있다고 생성되는 애가 아니라 호출될 때 가져온 인자를 가지고 초기화되면서 실행이 된다.

이 말은 즉슨 SoSimple ob = Obj와 같게된단 뜻이다.이때 복사생성자가 불리는것이다.
1)의 방법과 차이는 1)은 대놓고 대입하는 경우 2)는 함수의 매개변수를 이용해서 불리는경우다.

3)객체를 반환하되,참조형을 반환하지 않을 경우-->복사생성자가 두 번이여서 효율이 낮음
(반환형으로 참조형은 없다.)

ex)
MyClass TestFunc() //마이클래스형의 테스트펑크 함수 정의
{
MyClass temp; //마이클래스형의 temp 지역객체 선언
return temp; //temp를 마이클래스 형태로 리턴
}

int main()
{
MyClass a;
MyClass b(a);

MyClass c = TestFunc(); //테스트펑크의 결과값을 객체C를 만드는데 사용함
return0;
}

객체 C를 만드는데 TestFunc의 결과값을 쓸 수 있나? temp는 지역변수라 사라질탠데?
레퍼런스도 아닌데 가능한 이유는 바로 "임시객체"이기 때문이다.

return temp한 값이 MyClass c = TestFunc(); 여기에서 살아있어야한다.

보통 일반함수나 일반변수의 경우
int AddNum(int a,int b)
{
return a + b;
}

또는

int TestFunc2()
{
return 3;
}
같은 경우처럼 지역변수 규약이 적용되더라도 함수에 반환형이 있다면 그 자료형으로 복사해서 반환되고 지역변수는 사라진다.
(temp가 리턴되야하는데 여기선 자료형이 클래스고 temp는 지역변수여서 사라지지고 MyClass라는 객체를 리턴한 것 이때 이 객체는 temp값을 받아온 상태.
== temp가 메인으로 가야되는데 못가니깐 메인에 있을 객체를 임의적으로 하나만드는데 temp를 가지고 만들자.
== temp를 리턴하고싶은데 나는 수명이 여기서 끝이야 그러니깐 너가 객체 하나만들어 나를 가지고)

그리고 여기서 복사생성자가 한번 불린다. MyClass c = TestFunc();의 TestFunc()에
객체 MyClass가 들어가니깐

MyClass c = TestFunc();의 TestFunc()가 결과값이다.그 결과값은 곧 마이클래스 객체이고 결국은 MyClass c = MyClass();가 된다.temp의 값을 받아왔는데 이때 MyClass()를 임시로 만들어줬다해서 임시객체다.

근데 여기선 객체(TestFunc)의 이름이 없다.함수를 호출했지 MyClass와 관련된 별도의 객체를 만든적은 없으니깐 여기서 MyClass는 우리가 만든게 아니라 TestFunc에 의해 컴파일러가 임시적으로 하나 만들어준것이다.MyClass 객체는 다음줄을 넘어가면 사라지는 객체이다.(만들고 사용하는건 프로그래머가 하는거지 컴퓨터가해주는게 아니다.컴퓨터가 만들어놓은것을 사용할 수도 없음)

이제 두번째로 복사생성자가 불리는데 MyClass c = MyClass();와 같이 C가 임시객체를 가지고 선언 및 초기화가 되기때문이다.

이렇게 객체는 여러 방식으로 활용된다.복사생성자에도 활용, 상속 외 여러곳에서 활용됨

상속

C++에는 3가지 특징이 있다.상속, 캡슐화, 다형성
(이것도 베스트 면접질문 각 특징을 설명해보시오)

첫번째 특징(상속)
1. 상속의 정의
부모 클래스의 멤버 변수와 메소드를 자식 클래스가 그대로 재사용하는것

객체를 여러 개 정의해야하는 경우, 객체들 간의 상호작용을 표현해야하는데 그래픽만 갈아끼운 똑같은 객체들이 많음

예로들어 동물을 만든다고하면 동물 클래스로 하면 동물들 각각의 특징이 다르기에 컨트롤하기 어렵고 일일히 종류별로 클래스를 만든다고하면 호랑이 클래스, 토끼 클래스, 가젤 클래스 등등 일일히 다만들어야되는데 부모클래스가 이동속도같은 공통적인 특징들을 묶어가지고있는다면 자식클래스는 공통적인 특징들을 자동적으로 가지고 태어난다.

  1. 접근 제한자
    ㄱ. private : 오직 자신의 클래스안에서만 접근 가능한 멤버 변수, 메소드
    -> 함수들만 멤버변수에 접근이 가능했었음
    ㄴ. protected : 자신을 상속하는 클래스까지만 접근 가능
    -> 밖에서 접근 불가능.예외적으로 내가 부모클래스를 protected로 했으면 자식클래 스의 멤버함수에서 이 부모의 멤버변수를 바꾸는게 가능하다.
    ㄷ. public : 어떤 클래스에서나 접근 가능
    -> 밖에서 접근 가능

3.상속에는 접근제어자별로의 상속이 있는데 private 상속, protected 상속은 거의 안쓰고 public 상속을 쓴다.

ex)

#include < iostream >
using namespace std;

class A {
private:
int a;
protected:
int b;
public:
int c;
};

class B : public A { //멤버 변수의 접근 제한에 변화없음

};

int main() {
B b;

// a = private, b = protected, c = public
b.a;
b.b;
b.c;
}

: 는 초기화리스트에서 쓰이고 퍼블릭 상속에서도 쓰인다.
class B : public A는 "내가 B를 정의할건데 얘는 A라는 애를 상속받는애야"와 같다는걸 의미한다.== A를 상속받는애다. A앞에서 퍼블릭을 써주기!
이러면 A에 있는 멤버변수,함수가 B에 들어가게된다.

private:
int a;
protected:
int b;
public:
int c;

이게 접근제어자까지 그대로 B로 들어감

  • 이걸 별도로 써주면 안됨. 이미 B에는 데이터가 들어가있는건데 이게 또 들어가면 중복이 되는것임

B b; 상속받은 B로 b라는 객체를 만들었고 이 안에는 변수와 함수들이 들어있다.
근데 이제 이 안에 있는 변수들을 접근하는데 각 접근제어자가 다르기때문에 a랑 b는 접근을 못한다.쓸려면 PUBLIC에 함수를 정의해서 접근해야된다.

두번째 특징(캡슐화)
1.클래스 정의
우리가 클래스에서 멤버변수는 프라이빗에 배치하고 멤버함수는 퍼블릭에 배치한다고 했었는데 이유는 보안 및 협업시 건들지말아야될 데이터 때문(누군가 내가 의도한대로가 아닌 다른 의도로 쓸까봐)이라고 했음

멤버변수를 프라이빗으로 한번 감싸준게 곧 캡슐화!

2.예외처리( 면접 질문 베스트 )

그리고 추가로 우리가 프라이빗 멤버변수에 접근할려면 퍼블릭의 함수에서 접근할 수 밖에 없는데 함수를 짤때는 "예외처리"에 신경써줘야된다.(==상한선,하한선 조정)

예로 들어 자동차 속도계 함수에서 사용자가 일일히 수치를 입력하는만큼 속도가 나는게 아닌 내가 어느 이상의힘으로는 악셀을 밟아도 일정한 속도로만 가는 코드를 짜서 다른 사람이 코드를 잘못 사용못하게 막아놓는 이유(예외처리를 안해놓는다고하면 그 사람이 단위를 착각해서 큰 숫자를 넣으면 엄청 큰 속도가 들어갈 수도 있으니깐)에서도 캡슐화 같은 특징이 필요하다.또한 나중에 코드에 대한 책임에 분란이 있을때 도움이 되는게 나의 책임이 없다는걸 알려줄 수 있는 증거가 되기도한다.매번 회사에서 코드가 기록되기때문에 누가 바꾸면 그 사람 기록이 남는다. 잘못쓰는 사람까지 생각해서 완벽하게 짜놓자)

생명이나 사용자와 관련된 코드를 짤땐 예외처리가 중요하다.(자동으로 꺼지는 인덕션, 고데기 이런것 등등)

3.상속받은 다른 클래스의 구조

  • class B : public A가 아니라 class B : A 인 경우도 있는데 이 코드는 public이 앞에 생략된것이다.자동으로 디폴트값이 public 상속이라고 생각하면됨.

위 예시에서 A의 필드와 메서드를 다 가져왔는데 필드는 직접 접근을 못하고 들어있기만하고 들어있는 메서드는 그대로 써줄 수도 있고 일부만 써줄 수도 있다.

즉 상속받는 클래스에선 부모클래스의 퍼블릭 안 멤버함수들만 클래스 내부 코드에 갖다 쓸 수 있다.자식클래스에선 부모클래스랑 아예 다른 함수가 있을 수도 있는거고,부모클래스 퍼블릭의 함수 자체를 갖다쓸 수도 있고 결과값을 가져다 쓸 수도 있음.

여기서 중요한건 상속받은 자식 클래스인 B를 가지고 객체를 선언하면 A에 있던 코드도 있는거고 B 자체에서 새로 정의한 코드도 같이 있는 것이다.

그리고 이때 B에서 A의 함수를 호출하면 퍼블릭 함수에서 바뀐 결과값이 A의 멤버변수에 접근해서 값을 바꿔버릴수있다.(B자체 내에선 A의 멤버변수에 접근은 못함)

여기서 멤버변수가 바뀌었다는건 아까

int main 안의 B b; 이걸 선언하면서 b 객체 안에 들어있는 멤버변수 a, b가 바뀌었다는것이다.
선언하는 객체마다 멤버변수들은 다 다른것이다!!(하나가 아님)

profile
Emotional realizer

0개의 댓글