답변:
무슨 일이야
쓰면 자동 저장 시간을 가진 T t;
유형의 객체를 생성합니다 . 범위를 벗어나면 자동으로 정리됩니다.T
쓸 때 동적 저장 시간을 가진 new T()
유형의 객체를 생성합니다 . 자동으로 정리되지 않습니다.T
delete
정리하려면 포인터를 전달해야 합니다.
그러나 두 번째 예는 더 나쁩니다. 포인터를 역 참조하고 객체를 복사하는 것입니다. 이렇게하면으로 만든 객체에 대한 포인터를 잃을 new
수 있으므로 원하는 경우에도 삭제할 수 없습니다!
해야 할 일
자동 저장 기간을 선호해야합니다. 새로운 객체가 필요합니다.
A a; // a new object of type A
B b; // a new object of type B
동적 저장 기간이 필요한 경우 할당 된 객체에 대한 포인터를 자동 저장 기간 객체에 저장하면 자동으로 삭제됩니다.
template <typename T>
class automatic_pointer {
public:
automatic_pointer(T* pointer) : pointer(pointer) {}
// destructor: gets called upon cleanup
// in this case, we want to use delete
~automatic_pointer() { delete pointer; }
// emulate pointers!
// with this we can write *p
T& operator*() const { return *pointer; }
// and with this we can write p->f()
T* operator->() const { return pointer; }
private:
T* pointer;
// for this example, I'll just forbid copies
// a smarter class could deal with this some other way
automatic_pointer(automatic_pointer const&);
automatic_pointer& operator=(automatic_pointer const&);
};
automatic_pointer<A> a(new A()); // acts like a pointer, but deletes automatically
automatic_pointer<B> b(new B()); // acts like a pointer, but deletes automatically
이것은 잘 설명되지 않은 RAII ( 자원 획득이 초기화 ) 라는 일반적인 관용구입니다 . 정리가 필요한 리소스를 확보하면 자동 저장 기간이 걸리는 리소스에 고정되므로 정리에 대해 걱정할 필요가 없습니다. 이것은 모든 리소스, 메모리, 파일 열기, 네트워크 연결 또는 당신이 좋아하는 모든 것에 적용됩니다.
이 automatic_pointer
것은 이미 다양한 형태로 존재합니다. 예를 들어 제공했습니다. 라는 표준 라이브러리에 매우 유사한 클래스가 있습니다 std::unique_ptr
.
오래된 C ++ 11 이전 버전도 auto_ptr
있지만 이상한 복사 동작이 있기 때문에 더 이상 사용되지 않습니다.
그리고 std::shared_ptr
같은 객체에 대한 여러 포인터를 허용하고 마지막 포인터가 파괴 될 때만 정리하는 더 똑똑한 예제 가 있습니다.
*p += 2
일반 포인터를 사용하는 것처럼 예를 들어을 수행 할 수 있습니다 . 참조로 반환되지 않으면 일반적인 포인터의 동작을 모방하지 않을 것입니다. 이것은 의도입니다.
단계별 설명 :
// creates a new object on the heap:
new B()
// dereferences the object
*(new B())
// calls the copy constructor of B on the object
B object2 = *(new B());
따라서 이것의 끝에는 포인터가없는 힙에 객체가 있으므로 삭제할 수 없습니다.
다른 샘플 :
A *object1 = new A();
delete
할당 된 메모리 를 잊어 버린 경우에만 메모리 누수입니다 .
delete object1;
C ++에는 자동 스토리지가있는 오브젝트, 스택에서 자동으로 처리 된 오브젝트 및 힙에 동적 스토리지가있는 오브젝트 new
가 delete
있습니다. (이것은 모두 대략 넣어집니다)
에 delete
할당 된 모든 객체에 대해 있어야한다고 생각하십시오 new
.
편집하다
생각해 object2
보면 메모리 누수가 필요하지 않습니다.
다음 코드는 단지 포인트를 만들기위한 것입니다. 나쁜 생각입니다. 이런 코드를 좋아하지 마십시오.
class B
{
public:
B() {}; //default constructor
B(const B& other) //copy constructor, this will be called
//on the line B object2 = *(new B())
{
delete &other;
}
}
이 경우 other
참조로 전달 되므로 가 가리키는 정확한 객체가됩니다 new B()
. 따라서 주소를 가져오고 &other
포인터를 삭제하면 메모리가 해제됩니다.
그러나 나는 이것을 충분히 강조 할 수 없습니다. 요점은 바로 여기에 있습니다.
두 개의 "객체"가 주어지면 :
obj a;
obj b;
메모리에서 동일한 위치를 차지하지 않습니다. 다시 말해,&a != &b
하나의 값을 다른 값으로 지정해도 위치는 변경되지 않지만 내용은 변경됩니다.
obj a;
obj b = a;
//a == b, but &a != &b
직관적으로 포인터 "객체"는 같은 방식으로 작동합니다.
obj *a;
obj *b = a;
//a == b, but &a != &b
이제 예제를 보자.
A *object1 = new A();
이 값을 new A()
에 할당하고 object1
있습니다. 값은을 의미하는 포인터 object1 == new A()
이지만 &object1 != &(new A())
. (이 예제는 유효한 코드가 아니며 설명만을위한 것입니다)
포인터의 값이 유지되기 때문에 포인터가 가리키는 메모리를 해제 할 수 있습니다. delete object1;
규칙으로 인해 delete (new A());
누수가없는 것과 동일하게 동작합니다 .
두 번째 예에서는 뾰족한 개체를 복사합니다. 값은 실제 포인터가 아니라 해당 객체의 내용입니다. 다른 모든 경우와 마찬가지로 &object2 != &*(new A())
.
B object2 = *(new B());
할당 된 메모리에 대한 포인터를 잃어 버렸으므로 해제 할 수 없습니다. delete &object2;
작동하는 것처럼 보일 수 있지만에 &object2 != &*(new A())
해당 delete (new A())
하지 않으므로 유효하지 않습니다.
C # 및 Java에서는 new를 사용하여 모든 클래스의 인스턴스를 생성 한 다음 나중에 삭제하지 않아도됩니다.
C ++에는 객체를 만드는 키워드 "new"도 있지만 Java 나 C #과 달리 객체를 만드는 유일한 방법은 아닙니다.
C ++에는 객체를 생성하는 두 가지 메커니즘이 있습니다.
자동 작성을 사용하면 범위가 지정된 환경에서-함수에서 또는-클래스 (또는 구조체)의 멤버로 오브젝트를 작성합니다.
함수에서는 다음과 같이 생성합니다.
int func()
{
A a;
B b( 1, 2 );
}
클래스 내에서 일반적으로 다음과 같이 생성합니다.
class A
{
B b;
public:
A();
};
A::A() :
b( 1, 2 )
{
}
첫 번째 경우, 스코프 블록이 종료되면 오브젝트가 자동으로 삭제됩니다. 이것은 함수 또는 함수 내의 스코프 블록 일 수 있습니다.
후자의 경우, 객체 b는 객체 인 A의 인스턴스와 함께 파괴된다.
객체의 수명을 제어해야 할 때 객체에 새로운 객체가 할당 된 다음 객체를 삭제하려면 삭제가 필요합니다. RAII라는 기술을 사용하면 자동 객체 내에 넣어 객체를 생성하는 시점에서 객체 삭제를 처리하고 해당 자동 객체의 소멸자가 적용되기를 기다립니다.
그러한 객체 중 하나는 "삭제 자"로직을 호출하지만 객체를 공유하는 shared_ptr의 모든 인스턴스가 파괴 된 경우에만 shared_ptr입니다.
일반적으로 코드에 새로운 호출이 많이있을 수 있지만 삭제 호출은 제한적이어야하며 항상 스마트 포인터에 넣은 소멸자 또는 "삭제 자"객체에서 호출해야합니다.
소멸자는 절대 예외를 던져서는 안됩니다.
이렇게하면 메모리 누수가 거의 없습니다.
automatic
및 dynamic
. 또한 있습니다 static
.
B object2 = *(new B());
이 라인은 누출의 원인입니다. 이것을 조금 골라 보자 ..
object2는 주소 1에 저장된 B 유형의 변수입니다 (예, 임의의 숫자를 선택합니다). 오른쪽에서, 당신은 새로운 B 또는 B 타입의 객체에 대한 포인터를 요구했습니다. 프로그램은 기꺼이 당신에게 그것을주고 주소 2에 새로운 B를 할당하고 주소 3에 포인터를 만듭니다. 주소 2의 데이터에 액세스 할 수있는 유일한 방법은 주소 3의 포인터를 사용 *
하는 것입니다. 다음으로 포인터가 가리키는 데이터 (주소 2의 데이터)를 얻기 위해 포인터를 역 참조했습니다 . 이렇게하면 해당 데이터의 복사본이 효과적으로 생성되어 주소 1에 할당 된 object2에 할당됩니다. 원본이 아니라 사본임을 기억하십시오.
자, 여기 문제가 있습니다 :
실제로 사용할 수있는 위치에 포인터를 저장하지 않았습니다! 이 할당이 완료되면 포인터 (주소 2에 액세스하는 데 사용 된 주소 3의 메모리)가 범위를 벗어났습니다. 더 이상 delete를 호출 할 수 없으므로 address2의 메모리를 정리할 수 없습니다. 남아있는 것은 address1의 address2에서 데이터 사본입니다. 같은 것들 중 두 가지는 메모리에 앉아 있습니다. 하나는 액세스 할 수 있고 다른 하나는 액세스 할 수 없습니다 (경로를 잃었 기 때문에). 이것이 메모리 누수 인 이유입니다.
C # 배경에서 C ++의 포인터가 어떻게 작동하는지 많이 읽으라고 제안합니다. 그것들은 고급 주제이며 이해하는 데 시간이 걸릴 수 있지만 그 사용법은 매우 중요합니다.
더 쉬워지면 컴퓨터 메모리를 호텔처럼 생각하고 프로그램은 필요할 때 방을 고용하는 고객입니다.
이 호텔이 작동하는 방식은 방을 예약하고 떠날 때 포터에게 알리는 것입니다.
포터에게 알리지 않고 방을 예약하고 떠나면 포터는 그 방이 여전히 사용 중이라고 생각하고 다른 사람이 그 방을 사용하지 못하게합니다. 이 경우 공간 누출이 있습니다.
프로그램이 메모리를 할당하고 삭제하지 않으면 (단지 사용을 중지) 컴퓨터는 메모리가 여전히 사용 중이라고 생각하고 다른 사람이 메모리를 사용할 수 없게합니다. 이것은 메모리 누수입니다.
이것은 정확한 비유는 아니지만 도움이 될 수 있습니다.
생성 object2
할 때 새로 생성 한 객체의 복사본을 생성하지만 할당되지 않은 포인터도 손실되므로 나중에 삭제할 수있는 방법이 없습니다. 이를 피하려면 object2
참조를해야합니다.
글쎄, 어떤 시점에서 new
연산자를 사용하여 할당 한 메모리를 해당 메모리에 대한 포인터를 연산자에 전달 하여 할당하지 않으면 메모리 누수가 발생합니다 delete
.
위의 두 경우 :
A *object1 = new A();
여기서는 delete
메모리를 확보 하는 데 사용하지 않으므로 object1
포인터가 범위를 벗어나 면 포인터가 손실되어 delete
연산자를 사용할 수 없으므로 메모리 누수가 발생 합니다.
그리고 여기
B object2 = *(new B());
에서 반환 한 포인터를 삭제 new B()
하므로 delete
메모리를 비우기 위해 해당 포인터를 전달할 수 없습니다 . 따라서 다른 메모리 누수가 발생합니다.
즉시 누출되는 것은이 줄입니다.
B object2 = *(new B());
여기서는 B
힙에 새 객체를 만든 다음 스택에 복사본을 만듭니다. 힙에 할당 된 것은 더 이상 액세스 할 수 없으므로 누출이 발생합니다.
이 줄은 즉시 새지 않습니다.
A *object1 = new A();
당신이 결코 경우 누수가 될 delete
거라고하지 object1
하지만.