작성
·
294
5
virtual 키워드를 붙인다음에
PoweredDevice(power) 생성자는 왜 넣어주는지 잘 모르겠습니다.
강의내용에서도 그냥 붙여준다고만 나와서요
답변 4
13
안녕하세요.
PoweredDevice(power) 코드가 여러군데여서 어떤 부분을 말씀하시는건지 애매한데
강의 3:02 시간에서 Copier 클래스의 생성자에서 PoweredDevice(power) 을 호출하신 것을 말씀하시는 것 맞을까요?
Scanner와 Printer를 다중 상속 받는 손자인 Copier은 Scanner이 물려 받은 PoweredDevice 부분, Printer이 물려 받은 PoweredDevice 부분 이렇게 둘 다 받을 수 있게 됩니다. 즉, PoweredDevice 로부터 받은 상속 부분이 2 개나 생기는 것이죠. PoweredDevice 로부터 상속 받은 부분은 하나만 있으면 되는데 PoweredDevice 로 부터 상속 받은 것에 대한 메모리가 두 배로 생겨버렸으니 비효율적입니다.
이를 막아주는게 가상 상속입니다. 가상 상속을 하면 부모 타입의 메모리가 중복되지 않으면서 상속이 됩니다. 가상 상속된 할아버지 클래스의 생성자가 중복되어 호출되지 않도록, 가상 상속된 할아버지 클래스의 생성자는 손자 클래스에서 호출하게 되어 있습니다. 중복되어 호출되는 문제를 막기 위하여 중간에 있는 손자가 할아버지를 가상 상속시 두명의 부모는 자신들의 부모인 할아버지 생성자를 호출하지 않도록 처리가 됩니다.
따라서 가상 상속을 하면 Copier이 PoweredDevice 생성자를 호출하는 주체여야 하기 때문에 Copier에서 PoweredDevice(power) 를 적어주지 않으면 컴파일러가 자동으로 디폴트 생성자 PoweredDevice() 호출을 시도하게 됩니다. 그러나 PoweredDevice 클래스에 디폴트 생성자 PoweredDevice() 는 정의되어 있지 않고 매개 변수 하나가 있는 생성자만 가지고 있기 때문에 에러를 뱉었던 것입니다. (생성자가 아무것도 정의되어 있지않으면 컴파일러가 자동으로 디폴트 생성자를 만들어주지만, 매개 변수 있는 생성자가 하나라도 있다면 컴파일러는 디폴트 생성자를 자동으로 만들어주지 않고 생성자 호출이 필요하다고 에러를 뱉습니다.)
짧게 요약하자면 가상 상속으로 인하여 Copier이 PoweredDevice 생성자를 호출해야 하는데 생성자 호출을 안해줘서 컴파일러가 자동으로 디폴트 생성자를 만들어서 호출해주려고 했더니만 이미 매개변수 있는 생성자들이 있어서 그럴 수 없었기 때문에 에러가 발생했던 것입니다. 그래서 교수님께서 Copier 에서 PoweredDevice(power) 이렇게 직접 생성자를 호출하는 코드를 추가하여 해결하셨던 것입니다.
가상 상속으로 인하여 PowerdDevice 생성자를 호출하는 주체는 손자인 Copier이 되기에 위와 같이 이렇게 Copier에서 PoweredDevice 생성자 호출 안해주면, 컴파일러는 PowerdDevice 디폴트 생성자를 호출하려고 시도합니다.
그래서 디폴트 생성자를 추가로 정의해주면 Copier에서 PowerdDevice 생성자 호출코드 안써줘도 문제 없습니다. Copier 에서 PowerdDevice 생성자 호출 안 해주니까 컴파일러가 자동으로 PowerdDevice 디폴트 생성자를 호출한 것을 확인할 수 있습니다.
6
위 답변에서 말씀 드렸듯이, "가상 상속"을 받으면 두 부모에서 할아버지 생성자가 호출되지 않도로 합니다. 즉, Printer와 Scanner의 PowerDevice(somehintg)은 아예 실행되지 않습니다. 두 생성자를 호출하면 Copier 객체에서 메모리가 중복되버리기 때문입니다. 그래서 Copier에서 직접 PowerDevice(somehintg) 을 반드시 호출해야 하는데,(Copier이 PowerDevice로부터 대대로 물려받는 부분이 있으니 반드시 호출해 주어야 Copier객체가 완성되겠죠) PowerDevice(somehintg)를 Copier에 적어주지 않았으니까 컴파일러가 디폴트 생성자를 호출해주려고 시도하는 것입니다.
이처럼 가상 상속을 하면 손자 클래스에서 조상 클래스 생성자 호출을 부모들에게 맡기지 않고 본인이 직접 조상 클래스의 생성자들까지 직접 호출하도록 강제하기 때문에 다중 상속의 문제점을 예방할 수 있게 됩니다.
그리고 원래 컴파일러는 개발자가 생성자 호출 코드를 넣어주지 않으면 자동으로 디폴트 생성자를 호출시켜주는 일을 합니다. 생성자를 호출하지 않고 A a; 이렇게 객체만 만들어도 디폴트 생성자가 정의되어 있지 않다면 컴파일러에서 자동으로 A() 빈 디폴트 생성자를 만들어 호출해주고, 디폴트 생성자가 정의 되어 있다면 그 디폴트 생성자를 호출시켜 줍니다.
자식 객체가 생성이 되려면 부모로 부터 물려받은 부분을 먼저 생성 시켜야겠지요. 그래서 상속 받으면 부모 생성자도 반드시 호출해야 하는데 이를 안해주면 컴파일러가 이와 같은 원리로 자동으로 디폴트 생성자라도 호출을 합니다.
4
2021. 01. 20. 14:17
그렇군요 이제 이해가 됐습니다.
궁금한게 하나더 있는데
만약 scanner, printer 클래스의 생성자에서 부모 클래스인 PowerDevice(something) 생성자를 호출하고 있으면 손자 클래스인 copier 의 생성자에서 PowerDeivce(something) 생성자를 넣어주지 않아도 scanner, printer 의 생성자에서 PowerDevice(something) 을 호출해주니까 굳이 안넣어도 되는줄 알았는데 디폴트 생성자가 호출 되더라고요
상위 클래스에 상관없이 현재 자식 클래스에서 상위의 클래스들의 생성자를 호출해주지 않으면 무조건 디폴트 생성자가 호출되나요?
class scanner : virtual public PowerDevice {
...
scanner(something)
: PowerDevice(something)
{}
...
}
class printer : virtual public PowerDevice {
...
printer(something)
: PowerDevice(something)
{}
...
}
class copier : public scanner, public printer {
...
copier(something)
: scanner(something), printer(something)
{}
...
}