C ++에서 개체 파괴
C ++에서 객체가 정확히 언제 파괴되고, 그게 무슨 뜻입니까? 가비지 컬렉터가 없기 때문에 수동으로 제거해야합니까? 예외는 어떻게 작용합니까?
(참고 : 이것은 Stack Overflow의 C ++ FAQ에 대한 항목 입니다.이 양식으로 FAQ를 제공하는 아이디어를 비판하고 싶다면이 모든 것을 시작한 메타에 게시 할 수 있습니다. 이 질문은 FAQ 아이디어가 처음 시작된 C ++ 채팅룸 에서 모니터링 되므로 아이디어를 제안한 사람들이 답변을 읽을 가능성이 매우 높습니다.)
다음 텍스트에서는 소멸 시간이 둘러싸는 범위 (함수, 블록, 클래스, 표현식)에 의해 정적으로 결정되는 범위가 지정된 개체 와 일반적으로 런타임까지 정확한 소멸 시간을 알 수없는 동적 개체를 구분 합니다.
클래스 객체의 소멸 의미론은 소멸자에 의해 결정되지만 스칼라 객체의 소멸은 항상 작동하지 않습니다. 특히, 포인터 변수를 파괴해도 pointee가 파괴 되지는 않습니다 .
범위가 지정된 개체
자동 개체
자동 개체 (일반적으로 "로컬 변수"라고 함)는 제어 흐름이 정의 범위를 벗어날 때 정의의 역순으로 소멸됩니다.
void some_function()
{
Foo a;
Foo b;
if (some_condition)
{
Foo y;
Foo z;
} <--- z and y are destructed here
} <--- b and a are destructed here
함수 실행 중에 예외가 발생하면 예외가 호출자에게 전파되기 전에 이전에 생성 된 모든 자동 객체가 소멸됩니다. 이 프로세스를 스택 풀기 라고 합니다. 스택 해제 중에 앞서 언급 한 자동 개체의 소멸자를 더 이상 예외로 남겨 둘 수 없습니다. 그렇지 않으면 함수 std::terminate가 호출됩니다.
이것은 C ++에서 가장 중요한 지침 중 하나입니다.
소멸자는 절대로 던져서는 안됩니다.
로컬이 아닌 정적 개체
네임 스페이스 범위에 정의 된 정적 개체 (일반적으로 "전역 변수"라고 함) 및 정적 데이터 멤버는 다음을 실행 한 후 정의의 역순으로 삭제됩니다 main.
struct X
{
static Foo x; // this is only a *declaration*, not a *definition*
};
Foo a;
Foo b;
int main()
{
} <--- y, x, b and a are destructed here
Foo X::x; // this is the respective definition
Foo y;
다른 번역 단위에 정의 된 정적 개체의 상대적 구성 (및 소멸) 순서는 정의되지 않습니다.
예외가 정적 개체의 소멸자를 떠나면 함수 std::terminate가 호출됩니다.
로컬 정적 개체
함수 내부에 정의 된 정적 개체는 제어 흐름이 처음으로 정의를 통과 할 때 생성됩니다. 1 다음을 실행 한 후 역순으로 소멸됩니다 main.
Foo& get_some_Foo()
{
static Foo x;
return x;
}
Bar& get_some_Bar()
{
static Bar y;
return y;
}
int main()
{
get_some_Bar().do_something(); // note that get_some_Bar is called *first*
get_some_Foo().do_something();
} <--- x and y are destructed here // hence y is destructed *last*
예외가 정적 개체의 소멸자를 떠나면 함수 std::terminate가 호출됩니다.
1 : 이것은 매우 단순화 된 모델입니다. 정적 개체의 초기화 세부 사항은 실제로 훨씬 더 복잡합니다.
기본 클래스 하위 개체 및 멤버 하위 개체
제어 흐름이 개체의 소멸자 본문을 벗어나면 해당 멤버 하위 개체 ( "데이터 멤버"라고도 함)가 정의의 역순으로 소멸됩니다. 그 후, 기본 클래스 하위 객체는 기본 지정자 목록의 역순으로 소멸됩니다.
class Foo : Bar, Baz
{
Quux x;
Quux y;
public:
~Foo()
{
} <--- y and x are destructed here,
}; followed by the Baz and Bar base class subobjects
의 하위 개체 중 하나를 생성 하는 동안 예외가 발생 Foo하면 예외가 전파되기 전에 이전에 구성된 모든 하위 개체가 소멸됩니다. 반면에 Foo소멸자 는 개체가 완전히 생성되지 않았기 때문에 실행 되지 않습니다Foo .
소멸자 본문은 데이터 멤버 자체를 파괴 할 책임이 없습니다. 데이터 멤버가 객체가 소멸 될 때 해제되어야하는 리소스에 대한 핸들 인 경우에만 소멸자를 작성하면됩니다 (예 : 파일, 소켓, 데이터베이스 연결, 뮤텍스 또는 힙 메모리).
배열 요소
배열 요소는 내림차순으로 소멸됩니다. n 번째 요소를 생성 하는 동안 예외가 발생 하면 예외가 전파되기 전에 요소 n-1에서 0까지가 소멸됩니다.
임시 개체
클래스 유형의 prvalue 표현식이 평가 될 때 임시 객체가 생성됩니다. prvalue 표현식의 가장 눈에 띄는 예는 값으로 객체를 반환하는 함수 호출입니다 T operator+(const T&, const T&). 정상적인 상황에서 prvalue를 어휘 적으로 포함하는 전체 표현식이 완전히 평가되면 임시 객체가 파괴됩니다.
__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here
위의 함수 호출 some_function(a + " " + b)은 더 큰 표현식의 일부가 아니기 때문에 전체 표현식입니다 (대신 표현식 문의 일부입니다). 따라서 하위 식을 평가하는 동안 생성 된 모든 임시 개체는 세미콜론에서 삭제됩니다. 두 개의 임시 개체가 있습니다. 첫 번째는 첫 번째 추가 중에 구성되고 두 번째는 두 번째 추가 중에 구성됩니다. 두 번째 임시 개체는 첫 번째 개체보다 먼저 삭제됩니다.
두 번째 추가 중에 예외가 발생하면 첫 번째 임시 개체가 예외를 전파하기 전에 제대로 삭제됩니다.
로컬 참조가 prvalue 식으로 초기화되면 임시 개체의 수명이 로컬 참조의 범위로 확장되므로 매달린 참조를 얻을 수 없습니다.
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here
클래스가 아닌 유형의 prvalue 표현식이 평가되면 결과는 임시 객체 가 아니라 value 입니다. 그러나 prvalue가 참조를 초기화하는 데 사용되는 경우 임시 개체 가 생성됩니다.
const int& r = i + j;
동적 개체 및 배열
다음 섹션에서 X 파괴 는 "먼저 X를 파괴 한 다음 기본 메모리를 해제하는 것"을 의미합니다. 유사하게, create X 는 "먼저 충분한 메모리를 할당 한 다음 거기에 X를 생성"하는 것을 의미합니다.
동적 개체
를 통해 생성 된 동적 객체는를 통해 p = new Foo파괴됩니다 delete p. 을 잊어 버리면 delete p리소스 누수가 있습니다. 다음 중 하나는 정의되지 않은 동작으로 이어 지므로 시도해서는 안됩니다.
delete[](대괄호에주의)free또는 다른 수단을 통해 동적 개체를 파괴- 동적 객체를 여러 번 파괴
- 파괴 된 후 동적 객체에 액세스
동적 개체를 생성 하는 동안 예외가 발생 하면 예외가 전파되기 전에 기본 메모리가 해제됩니다. ( 객체가 완전히 생성되지 않았기 때문에 소멸자는 메모리 해제 이전에 실행 되지 않습니다 .)
동적 배열
을 통해 생성 된 동적 배열은 다음을 통해 p = new Foo[n]파괴됩니다 delete[] p(대괄호 참고). 을 잊어 버리면 delete[] p리소스 누수가 있습니다. 다음 중 하나는 정의되지 않은 동작으로 이어 지므로 시도해서는 안됩니다.
- 통한 동적 배열 파괴
delete,free또는 다른 수단 - 동적 배열을 여러 번 파괴
- 파괴 된 후 동적 배열에 액세스
n 번째 요소를 생성 하는 동안 예외가 발생하면 n-1부터 0까지의 요소가 내림차순으로 소멸되고 기본 메모리가 해제되고 예외가 전파됩니다.
(일반적으로 동적 배열 std::vector<Foo>보다 선호해야 Foo*합니다. 정확하고 강력한 코드를 훨씬 쉽게 작성할 수 있습니다.)
참조 계산 스마트 포인터
여러 std::shared_ptr<Foo>개체에 의해 관리되는 동적 개체는 std::shared_ptr<Foo>해당 동적 개체 공유와 관련된 마지막 개체가 소멸되는 동안 소멸됩니다 .
(일반적으로 공유 객체 std::shared_ptr<Foo>보다 선호해야 Foo*합니다. 정확하고 강력한 코드를 훨씬 쉽게 작성할 수 있습니다.)
개체의 소멸자는 개체 수명이 끝나고 소멸 될 때 자동으로 호출됩니다. 일반적으로 수동으로 호출하면 안됩니다.
이 개체를 예제로 사용합니다.
class Test
{
public:
Test() { std::cout << "Created " << this << "\n";}
~Test() { std::cout << "Destroyed " << this << "\n";}
Test(Test const& rhs) { std::cout << "Copied " << this << "\n";}
Test& operator=(Test const& rhs) { std::cout << "Assigned " << this << "\n";}
};
C ++에는 세 가지 (C ++ 11에서는 네 가지) 고유 한 유형의 개체가 있으며 개체의 유형은 개체 수명을 정의합니다.
- 정적 저장 기간 개체
- 자동 저장 기간 개체
- 동적 저장 기간 개체
- (C ++ 11에서) 스레드 스토리지 기간 개체
정적 저장 기간 개체
이것은 가장 간단하고 전역 변수와 동일합니다. 이러한 개체의 수명은 일반적으로 응용 프로그램의 길이입니다. 이것들은 (보통) main이 들어가기 전에 생성되고 우리가 main을 종료 한 후 생성되는 역순으로 파괴됩니다.
Test global;
int main()
{
std::cout << "Main\n";
}
> ./a.out
Created 0x10fbb80b0
Main
Destroyed 0x10fbb80b0
참고 1 : 두 가지 다른 유형의 정적 저장 기간 개체가 있습니다.
클래스의 정적 멤버 변수.
이것들은 모든 의미와 목적을 위해 수명 측면에서 전역 변수와 동일합니다.
함수 내부의 정적 변수.
이들은 느리게 생성 된 정적 저장 기간 개체입니다. 처음 사용할 때 생성됩니다 (C ++ 11의 스레드 안전 매너에서). 다른 정적 저장 기간 개체와 마찬가지로 응용 프로그램이 종료되면 소멸됩니다.
건설 / 파괴 순서
- 컴파일 단위 내의 구성 순서는 잘 정의되어 있으며 선언과 동일합니다.
- 컴파일 단위 간의 구성 순서는 정의되어 있지 않습니다.
- 파괴 순서는 건설 순서의 정반대입니다.
자동 저장 기간 개체
이들은 가장 일반적인 유형의 개체이며 99 %의 시간 동안 사용해야하는 개체입니다.
다음은 세 가지 주요 자동 변수 유형입니다.
- 함수 / 블록 내부의 지역 변수
- 클래스 / 배열 내부의 멤버 변수.
- 임시 변수.
지역 변수
함수 / 블록이 종료되면 해당 함수 / 블록 내부에 선언 된 모든 변수가 파괴됩니다 (생성의 역순).
int main()
{
std::cout << "Main() START\n";
Test scope1;
Test scope2;
std::cout << "Main Variables Created\n";
{
std::cout << "\nblock 1 Entered\n";
Test blockScope;
std::cout << "block 1 about to leave\n";
} // blockScope is destrpyed here
{
std::cout << "\nblock 2 Entered\n";
Test blockScope;
std::cout << "block 2 about to leave\n";
} // blockScope is destrpyed here
std::cout << "\nMain() END\n";
}// All variables from main destroyed here.
> ./a.out
Main() START
Created 0x7fff6488d938
Created 0x7fff6488d930
Main Variables Created
block 1 Entered
Created 0x7fff6488d928
block 1 about to leave
Destroyed 0x7fff6488d928
block 2 Entered
Created 0x7fff6488d918
block 2 about to leave
Destroyed 0x7fff6488d918
Main() END
Destroyed 0x7fff6488d930
Destroyed 0x7fff6488d938
멤버 변수
멤버 변수의 수명은이를 소유 한 개체에 바인딩됩니다. 소유자 수명이 종료되면 모든 구성원 수명도 종료됩니다. 따라서 동일한 규칙을 준수하는 소유자의 생애를 살펴볼 필요가 있습니다.
참고 : 구성원은 항상 생성의 역순으로 소유자보다 먼저 파기됩니다.
- 따라서 클래스 멤버의 경우 선언 순서대로 생성되고 선언
의 역순으로 소멸됩니다. - Thus for array members they are created in order 0-->top
and destroyed in the reverse order top-->0
temporary variables
These are objects that are created as the result of an expression but are not assigned to a variable. Temporary variables are destroyed just like other automatic variables. It is just that the end of their scope is the end of the statement in which they are created (this is usally the ';').
std::string data("Text.");
std::cout << (data + 1); // Here we create a temporary object.
// Which is a std::string with '1' added to "Text."
// This object is streamed to the output
// Once the statement has finished it is destroyed.
// So the temporary no longer exists after the ';'
Note: There are situations where the life of a temporary can be extended.
But this is not relevant to this simple discussion. By the time you understand that this document will be second nature to you and before it is extending the life of a temporary is not something you want to do.
Dynamic Storage duration objects
These objects have a dynamic lifespan and are created with new and destroyed with a call to delete.
int main()
{
std::cout << "Main()\n";
Test* ptr = new Test();
delete ptr;
std::cout << "Main Done\n";
}
> ./a.out
Main()
Created 0x1083008e0
Destroyed 0x1083008e0
Main Done
For devs that come from garbage collected languages this can seem strange (managing the lifespan of your object). But the problem is not as bad as it seems. It is unusual in C++ to use dynamically allocated objects directly. We have management objects to control their lifespan.
The closest thing to most other GC collected languages is the std::shared_ptr. This will keep track of the number of users of a dynamically created object and when all of them are gone will call delete automatically (I think of this as a better version of a normal Java object).
int main()
{
std::cout << "Main Start\n";
std::shared_ptr<Test> smartPtr(new Test());
std::cout << "Main End\n";
} // smartPtr goes out of scope here.
// As there are no other copies it will automatically call delete on the object
// it is holding.
> ./a.out
Main Start
Created 0x1083008e0
Main Ended
Destroyed 0x1083008e0
Thread Storage duration objects
These are new to the language. They are very much like static storage duration objects. But rather than living the same life as the application they live as long as the thread of execution they are associated with.
참고URL : https://stackoverflow.com/questions/6403055/object-destruction-in-c
'Program Club' 카테고리의 다른 글
| Haskell 기능이 정확성 속성으로 입증 / 모델 검사 / 검증 될 수 있습니까? (0) | 2020.11.14 |
|---|---|
| float 값에 "f"접미사? (0) | 2020.11.14 |
| 'catch'블록없이 'try-finally'블록 사용 (0) | 2020.11.14 |
| 백본 모델에서 초기화와 생성자의 차이점은 무엇입니까 (0) | 2020.11.14 |
| iOS 개발을 위해 Xcode 대신 (또는 함께) Vim 사용 (0) | 2020.11.14 |