etc./StackOverFlow

가상 소멸자는 언제 사용합니까?

청렴결백한 만능 재주꾼 2022. 1. 14. 12:32
반응형

질문자 :Lodle


OOP 이론에 대해 확실히 이해하고 있지만 나를 많이 혼란스럽게 하는 것은 가상 소멸자입니다.

나는 소멸자가 체인의 모든 객체에 관계없이 항상 호출된다고 생각했습니다.

언제 가상으로 만들 예정이며 그 이유는 무엇입니까?



가상 소멸자는 기본 클래스에 대한 포인터를 통해 파생 클래스의 인스턴스를 잠재적으로 삭제할 수 있는 경우에 유용합니다.

 class Base { // some virtual methods }; class Derived : public Base { ~Derived() { // Do some important cleanup } };

virtual 선언하지 않았음을 알 수 있습니다. 이제 다음 스니펫을 살펴보겠습니다.

 Base *b = new Derived(); // use b delete b; // Here's the problem!

Base의 소멸자는 virtual bDerived 객체를 가리키는 Base* delete b 에는 정의되지 않은 동작이 있습니다 .

[ delete b ]에서 삭제할 객체의 정적 유형이 동적 유형과 다른 경우 정적 유형은 삭제할 객체의 동적 유형의 기본 클래스가 되고 정적 유형에는 가상 소멸자가 있어야 합니다. 또는 동작이 정의되지 않았습니다 .

대부분의 구현에서 소멸자 호출은 비가상 코드처럼 해결됩니다. 즉, 기본 클래스의 소멸자가 호출되지만 파생 클래스의 소멸자는 호출되지 않아 리소스 누수가 발생합니다.

요약하자면, 기본 클래스의 소멸자는 다형성으로 조작되어야 하는 경우 virtual

기본 클래스 포인터를 통해 인스턴스가 삭제되는 것을 방지하려면 기본 클래스 소멸자를 보호 및 비가상으로 만들 수 있습니다. 이렇게 하면 컴파일러에서 기본 클래스 포인터에서 delete 를 호출할 수 없습니다.

Herb Sutter의 이 기사에서 가상 및 가상 기본 클래스 소멸자에 대해 자세히 알아볼 수 있습니다.


Luc Touraille

가상 생성자는 불가능하지만 가상 소멸자는 가능합니다. 실험해보자.......

 #include <iostream> using namespace std; class Base { public: Base(){ cout << "Base Constructor Called\n"; } ~Base(){ cout << "Base Destructor called\n"; } }; class Derived1: public Base { public: Derived1(){ cout << "Derived constructor called\n"; } ~Derived1(){ cout << "Derived destructor called\n"; } }; int main() { Base *b = new Derived1(); delete b; }

위의 코드는 다음을 출력합니다.

 Base Constructor Called Derived constructor called Base Destructor called

파생 객체의 구성은 구성 규칙을 따르지만 "b" 포인터(기본 포인터)를 삭제하면 기본 소멸자만 호출된다는 것을 알 수 있습니다. 그러나 이런 일이 일어나서는 안 됩니다. 적절한 작업을 수행하려면 기본 소멸자를 가상으로 만들어야 합니다. 이제 다음에서 어떤 일이 일어나는지 봅시다.

 #include <iostream> using namespace std; class Base { public: Base(){ cout << "Base Constructor Called\n"; } virtual ~Base(){ cout << "Base Destructor called\n"; } }; class Derived1: public Base { public: Derived1(){ cout << "Derived constructor called\n"; } ~Derived1(){ cout << "Derived destructor called\n"; } }; int main() { Base *b = new Derived1(); delete b; }

출력이 다음과 같이 변경되었습니다.

 Base Constructor Called Derived Constructor called Derived destructor called Base destructor called

따라서 기본 포인터의 소멸(파생 객체에 대한 할당을 취합니다!)은 소멸 규칙을 따릅니다. 즉, 먼저 파생된 다음 기본이 됩니다. 반면에 가상 생성자와 같은 것은 없습니다.


Tunvir Rahman Tusher

다형성 기본 클래스에서 소멸자를 가상으로 선언합니다. 이것은 Scott Meyers의 Effective C++의 항목 7입니다. 마이어스는 클래스가 가상 기능이있는 경우, 그것은 가상 소멸자를 가져야한다고 요약에 간다, 그 클래스는 기본 클래스로 설계되지 또는 다형 적으로 사용되는 가상 소멸자를하지를 선언해야 설계되지 않았습니다.


Bill the Lizard

또한 가상 소멸자가 없을 때 기본 클래스 포인터를 삭제하면 정의되지 않은 동작이 발생 합니다. 내가 최근에 배운 것:

C++에서 삭제 재정의는 어떻게 작동해야 합니까?

나는 수년간 C++를 사용해 왔지만 여전히 스스로 목을 매고 있습니다.


BigSandwich

클래스가 다형성일 때마다 소멸자를 가상으로 만드십시오.


yesraaj

기본 클래스에 대한 포인터를 통해 소멸자 호출

 struct Base { virtual void f() {} virtual ~Base() {} }; struct Derived : Base { void f() override {} ~Derived() override {} }; Base* base = new Derived; base->f(); // calls Derived::f base->~Base(); // calls Derived::~Derived

가상 소멸자 호출은 다른 가상 함수 호출과 다르지 않습니다.

base->f() 의 경우 호출이 Derived::f() base->~Base() - 재정의 함수인 Derived::~Derived() 가 호출됩니다.

소멸자가 간접적으로 호출될 때도 마찬가지입니다(예: delete base; . delete 문은 Derived::~Derived() 로 전달될 base->~Base() 를 호출합니다.

비가상 소멸자가 있는 추상 클래스

기본 클래스에 대한 포인터를 통해 객체를 삭제하지 않으려면 가상 소멸자가 필요하지 않습니다. 실수로 호출되지 않도록 protected

 // library.hpp struct Base { virtual void f() = 0; protected: ~Base() = default; }; void CallsF(Base& base); // CallsF is not going to own "base" (ie call "delete &base;"). // It will only call Base::f() so it doesn't need to access Base::~Base. //------------------- // application.cpp struct Derived : Base { void f() override { ... } }; int main() { Derived derived; CallsF(derived); // No need for virtual destructor here as well. }

Abyx

간단히 말해서 가상 소멸자는 파생 클래스 개체를 가리키는 기본 클래스 포인터를 삭제할 때 리소스를 적절한 순서로 파괴하는 것입니다.

 #include<iostream> using namespace std; class B{ public: B(){ cout<<"B()\n"; } virtual ~B(){ cout<<"~B()\n"; } }; class D: public B{ public: D(){ cout<<"D()\n"; } ~D(){ cout<<"~D()\n"; } }; int main(){ B *b = new D(); delete b; return 0; } OUTPUT: B() D() ~D() ~B() ============== If you don't give ~B() as virtual. then output would be B() D() ~B() where destruction of ~D() is not done which leads to leak


Prakash GiBBs

기본 클래스 포인터를 통해 객체가 삭제되는 동안 다른 소멸자가 적절한 순서를 따라야 할 때 소멸자에 대한 가상 키워드가 필요합니다. 예를 들어:

 Base *myObj = new Derived(); // Some code which is using myObj object myObj->fun(); //Now delete the object delete myObj ;

기본 클래스 소멸자가 가상이면 객체는 순서대로 소멸됩니다(먼저 파생된 객체 다음 기본). 기본 클래스 소멸자가 가상이 아닌 경우 기본 클래스 개체만 삭제됩니다(포인터가 기본 클래스 "Base *myObj"이기 때문에). 따라서 파생 개체에 대한 메모리 누수가 발생합니다.


Mukul Kashmira

저는 인터페이스와 인터페이스의 구현에 대해 생각하는 것을 좋아합니다. C++에서 인터페이스는 순수 가상 클래스입니다. 소멸자는 인터페이스의 일부이며 구현될 것으로 예상됩니다. 따라서 소멸자는 순수 가상이어야 합니다. 생성자는 어떻습니까? 객체는 항상 명시적으로 인스턴스화되기 때문에 생성자는 실제로 인터페이스의 일부가 아닙니다.


Dragan Ostojic

가상 소멸자란 무엇이며 가상 소멸자를 사용하는 방법

클래스 소멸자는 클래스에 의해 할당된 메모리를 재할당하는 ~ 앞에 클래스 이름이 같은 함수입니다. 가상 소멸자가 필요한 이유

일부 가상 기능이 있는 다음 샘플을 참조하십시오.

샘플은 또한 문자를 대문자 또는 소문자로 변환하는 방법을 알려줍니다.

 #include "stdafx.h" #include<iostream> using namespace std; // program to convert the lower to upper orlower class convertch { public: //void convertch(){}; virtual char* convertChar() = 0; ~convertch(){}; }; class MakeLower :public convertch { public: MakeLower(char *passLetter) { tolower = true; Letter = new char[30]; strcpy(Letter, passLetter); } virtual ~MakeLower() { cout<< "called ~MakeLower()"<<"\n"; delete[] Letter; } char* convertChar() { size_t len = strlen(Letter); for(int i= 0;i<len;i++) Letter[i] = Letter[i] + 32; return Letter; } private: char *Letter; bool tolower; }; class MakeUpper : public convertch { public: MakeUpper(char *passLetter) { Letter = new char[30]; toupper = true; strcpy(Letter, passLetter); } char* convertChar() { size_t len = strlen(Letter); for(int i= 0;i<len;i++) Letter[i] = Letter[i] - 32; return Letter; } virtual ~MakeUpper() { cout<< "called ~MakeUpper()"<<"\n"; delete Letter; } private: char *Letter; bool toupper; }; int _tmain(int argc, _TCHAR* argv[]) { convertch *makeupper = new MakeUpper("hai"); cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" "; delete makeupper; convertch *makelower = new MakeLower("HAI");; cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; delete makelower; return 0; }

위의 샘플에서 MakeUpper 및 MakeLower 클래스 모두에 대한 소멸자가 호출되지 않는 것을 볼 수 있습니다.

가상 소멸자를 사용하여 다음 샘플 보기

 #include "stdafx.h" #include<iostream> using namespace std; // program to convert the lower to upper orlower class convertch { public: //void convertch(){}; virtual char* convertChar() = 0; virtual ~convertch(){}; // defined the virtual destructor }; class MakeLower :public convertch { public: MakeLower(char *passLetter) { tolower = true; Letter = new char[30]; strcpy(Letter, passLetter); } virtual ~MakeLower() { cout<< "called ~MakeLower()"<<"\n"; delete[] Letter; } char* convertChar() { size_t len = strlen(Letter); for(int i= 0;i<len;i++) { Letter[i] = Letter[i] + 32; } return Letter; } private: char *Letter; bool tolower; }; class MakeUpper : public convertch { public: MakeUpper(char *passLetter) { Letter = new char[30]; toupper = true; strcpy(Letter, passLetter); } char* convertChar() { size_t len = strlen(Letter); for(int i= 0;i<len;i++) { Letter[i] = Letter[i] - 32; } return Letter; } virtual ~MakeUpper() { cout<< "called ~MakeUpper()"<<"\n"; delete Letter; } private: char *Letter; bool toupper; }; int _tmain(int argc, _TCHAR* argv[]) { convertch *makeupper = new MakeUpper("hai"); cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n"; delete makeupper; convertch *makelower = new MakeLower("HAI");; cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n "; delete makelower; return 0; }

가상 소멸자는 클래스의 가장 파생된 런타임 소멸자를 명시적으로 호출하여 적절한 방식으로 개체를 지울 수 있도록 합니다.

또는 링크를 방문하십시오

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138


user2578542

가상 기본 클래스 소멸자는 "모범 사례"입니다. 메모리 누수를 피하기 위해 항상 사용해야 합니다. 그것들을 사용하면 클래스의 상속 체인에 있는 모든 소멸자가 (적절한 순서로) 호출되고 있는지 확인할 수 있습니다. 가상 소멸자를 사용하여 기본 클래스에서 상속하면 상속하는 클래스의 소멸자도 자동으로 가상이 됩니다(따라서 상속 클래스 소멸자 선언에서 '가상'을 다시 입력할 필요가 없음).


Trantor

이 질문의 핵심은 소멸자가 아니라 가상 메서드와 다형성에 관한 것입니다. 다음은 더 명확한 예입니다.

 class A { public: A() {} virtual void foo() { cout << "This is A." << endl; } }; class B : public A { public: B() {} void foo() { cout << "This is B." << endl; } }; int main(int argc, char* argv[]) { A *a = new B(); a->foo(); if(a != NULL) delete a; return 0; }

다음을 인쇄합니다:

 This is B.

virtual 이 없으면 다음과 같이 인쇄됩니다.

 This is A.

이제 가상 소멸자를 사용해야 하는 경우를 이해해야 합니다.


gonjay

shared_ptr (unique_ptr이 아닌 shared_ptr만 사용)을 사용하는 경우 기본 클래스 소멸자 가상을 가질 필요가 없습니다.

 #include <iostream> #include <memory> using namespace std; class Base { public: Base(){ cout << "Base Constructor Called\n"; } ~Base(){ // not virtual cout << "Base Destructor called\n"; } }; class Derived: public Base { public: Derived(){ cout << "Derived constructor called\n"; } ~Derived(){ cout << "Derived destructor called\n"; } }; int main() { shared_ptr<Base> b(new Derived()); }

산출:

 Base Constructor Called Derived constructor called Derived destructor called Base Destructor called

Zhenxiao Hao

기본 클래스에서 파생 클래스 소멸자를 호출해야 할 때. 기본 클래스에서 가상 기본 클래스 소멸자를 선언해야 합니다.


user2641018

가상 소멸자 없이 기본 클래스(/구조체)를 통해 삭제할 때 발생할 수 있는 "정의되지 않은" 동작 또는 최소한 vtable이 없는 경우 발생할 수 있는 "충돌" 동작에 대해 논의하는 것이 도움이 될 것이라고 생각했습니다. 아래 코드는 몇 가지 간단한 구조체를 나열합니다(클래스의 경우에도 마찬가지임).

 #include <iostream> using namespace std; struct a { ~a() {} unsigned long long i; }; struct b : a { ~b() {} unsigned long long j; }; struct c : b { ~c() {} virtual void m3() {} unsigned long long k; }; struct d : c { ~d() {} virtual void m4() {} unsigned long long l; }; int main() { cout << "sizeof(a): " << sizeof(a) << endl; cout << "sizeof(b): " << sizeof(b) << endl; cout << "sizeof(c): " << sizeof(c) << endl; cout << "sizeof(d): " << sizeof(d) << endl; // No issue. a* a1 = new a(); cout << "a1: " << a1 << endl; delete a1; // No issue. b* b1 = new b(); cout << "b1: " << b1 << endl; cout << "(a*) b1: " << (a*) b1 << endl; delete b1; // No issue. c* c1 = new c(); cout << "c1: " << c1 << endl; cout << "(b*) c1: " << (b*) c1 << endl; cout << "(a*) c1: " << (a*) c1 << endl; delete c1; // No issue. d* d1 = new d(); cout << "d1: " << d1 << endl; cout << "(c*) d1: " << (c*) d1 << endl; cout << "(b*) d1: " << (b*) d1 << endl; cout << "(a*) d1: " << (a*) d1 << endl; delete d1; // Doesn't crash, but may not produce the results you want. c1 = (c*) new d(); delete c1; // Crashes due to passing an invalid address to the method which // frees the memory. d1 = new d(); b1 = (b*) d1; cout << "d1: " << d1 << endl; cout << "b1: " << b1 << endl; delete b1; /* // This is similar to what's happening above in the "crash" case. char* buf = new char[32]; cout << "buf: " << (void*) buf << endl; buf += 8; cout << "buf after adding 8: " << (void*) buf << endl; delete buf; */ }

가상 소멸자가 필요한지 여부를 제안하는 것은 아니지만 일반적으로 사용하는 것이 좋습니다. 기본 클래스(/struct)에 vtable이 없고 파생 클래스(/struct)가 있고 기본 클래스(/struct)를 통해 개체를 삭제하면 충돌이 발생할 수 있는 이유를 지적하고 있습니다. 바늘. 이 경우 힙의 자유 루틴에 전달한 주소가 유효하지 않으므로 충돌이 발생합니다.

위의 코드를 실행하면 언제 문제가 발생하는지 명확하게 알 수 있습니다. 기본 클래스(/struct)의 this 포인터가 파생 클래스(/struct)의 this 포인터와 다를 때 이 문제가 발생합니다. 위의 샘플에서 struct 및 b에는 vtable이 없습니다. 구조체 c와 d에는 vtable이 있습니다. 따라서 ac 또는 d 개체 인스턴스에 대한 a 또는 b 포인터는 vtable을 설명하도록 수정됩니다. 삭제하기 위해 this 또는 b 포인터를 전달하면 힙의 자유 루틴에 유효하지 않은 주소로 인해 충돌이 발생합니다.

기본 클래스 포인터에서 vtable이 있는 파생 인스턴스를 삭제하려는 경우 기본 클래스에 vtable이 있는지 확인해야 합니다. 이를 수행하는 한 가지 방법은 리소스를 적절하게 정리하기 위해 원하는 가상 소멸자를 추가하는 것입니다.


nickdu

virtual 에 대한 기본 정의는 클래스의 멤버 함수가 파생 클래스에서 재정의될 수 있는지 여부를 결정하는 것입니다.

클래스의 D-tor는 기본적으로 범위의 끝에서 호출되지만 문제가 있습니다. 예를 들어 힙에 인스턴스를 정의할 때(동적 할당) 수동으로 삭제해야 합니다.

명령어가 실행되자마자 기본 클래스 소멸자가 호출되지만 파생된 소멸자는 호출되지 않습니다.

실제 예는 제어 필드에서 이펙터, 액추에이터를 조작해야 하는 경우입니다.

스코프의 끝에서 파워 요소(Actuator) 중 하나의 소멸자가 호출되지 않으면 치명적인 결과가 발생합니다.

 #include <iostream> class Mother{ public: Mother(){ std::cout<<"Mother Ctor"<<std::endl; } virtual~Mother(){ std::cout<<"Mother D-tor"<<std::endl; } }; class Child: public Mother{ public: Child(){ std::cout<<"Child C-tor"<<std::endl; } ~Child(){ std::cout<<"Child D-tor"<<std::endl; } }; int main() { Mother *c = new Child(); delete c; return 0; }

rekkalmd

공개적으로 상속되는 모든 클래스는 다형성이든 아니든 가상 소멸자가 있어야 합니다. 다시 말해서 기본 클래스 포인터가 포인터를 가리킬 수 있는 경우 기본 클래스에 가상 소멸자가 있어야 합니다.

가상인 경우 파생 클래스 소멸자가 호출된 다음 기본 클래스 소멸자가 호출됩니다. 가상이 아니면 기본 클래스 소멸자만 호출됩니다.


Syed H

출처 : http:www.stackoverflow.com/questions/461203/when-to-use-virtual-destructors

반응형