C ++에서 동적으로 할당 된 개체에 대한 포인터 벡터를 사용할 때 메모리 누수를 피하는 방법은 무엇입니까?
객체에 대한 포인터 벡터를 사용하고 있습니다. 이러한 개체는 기본 클래스에서 파생되며 동적으로 할당 및 저장됩니다.
예를 들어 다음과 같습니다.
vector<Enemy*> Enemies;
Enemy 클래스에서 파생 된 다음 다음과 같이 파생 클래스에 대한 메모리를 동적으로 할당합니다.
enemies.push_back(new Monster());
메모리 누수 및 기타 문제를 방지하기 위해 알아야 할 사항은 무엇입니까?
std::vector
항상 그렇듯이 메모리를 관리하지만이 메모리는 개체가 아닌 포인터로 구성됩니다.
이것이 의미하는 것은 벡터가 범위를 벗어나면 클래스가 메모리에서 손실된다는 것입니다. 예를 들면 :
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<base*> container;
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.push_back(new derived());
} // leaks here! frees the pointers, doesn't delete them (nor should it)
int main()
{
foo();
}
해야 할 일은 벡터가 범위를 벗어나기 전에 모든 객체를 삭제하는 것입니다.
#include <algorithm>
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<base*> container;
template <typename T>
void delete_pointed_to(T* const ptr)
{
delete ptr;
}
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.push_back(new derived());
// free memory
std::for_each(c.begin(), c.end(), delete_pointed_to<base>);
}
int main()
{
foo();
}
그러나 이것은 우리가 어떤 행동을 수행하는 것을 기억해야하기 때문에 유지하기가 어렵습니다. 더 중요한 것은 요소 할당과 할당 해제 루프 사이에 예외가 발생하는 경우 할당 해제 루프가 실행되지 않고 어쨌든 메모리 누수 문제가 발생합니다! 이를 예외 안전이라고하며 할당 해제가 자동으로 수행되어야하는 중요한 이유입니다.
포인터가 스스로 삭제되면 더 좋습니다. 이를 스마트 포인터라고하며 표준 라이브러리는 std::unique_ptr
및 std::shared_ptr
.
std::unique_ptr
일부 리소스에 대한 고유 한 (공유되지 않은 단일 소유자) 포인터를 나타냅니다. 이것은 기본 스마트 포인터 여야하며 원시 포인터 사용을 전체적으로 완전히 대체해야합니다.
auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself
std::make_unique
감독에 의해 C ++ 11 표준에서 누락되었지만 직접 만들 수 있습니다. 직접 생성하려면 unique_ptr
(가능한 경우 권장하지 않음 make_unique
) 다음과 같이하십시오.
std::unique_ptr<derived> myresource(new derived());
고유 포인터에는 이동 의미 만 있습니다. 복사 할 수 없습니다.
auto x = myresource; // error, cannot copy
auto y = std::move(myresource); // okay, now myresource is empty
그리고 이것이 컨테이너에서 사용하는 데 필요한 전부입니다.
#include <memory>
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<std::unique_ptr<base>> container;
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.push_back(make_unique<derived>());
} // all automatically freed here
int main()
{
foo();
}
shared_ptr
참조 카운팅 복사 의미가 있습니다. 여러 소유자가 개체를 공유 할 수 있습니다. shared_ptr
개체에 대해 얼마나 많은 s가 존재 하는지 추적 하고 마지막 개체가 존재하지 않을 때 (해당 개수가 0이 됨) 포인터를 해제합니다. 복사는 단순히 참조 횟수를 증가시킵니다 (이동하면 거의 무료 비용으로 소유권이 이전됩니다). 당신은 그들을 만들기 std::make_shared
(이상 같이 있기 때문에 직접하지만, shared_ptr
내부적으로 할당을하는, 그것은 일반적으로 더 효율적이고 기술적으로의 더 예외 안전한 사용에 make_shared
).
#include <memory>
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<std::shared_ptr<base>> container;
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.push_back(std::make_shared<derived>());
} // all automatically freed here
int main()
{
foo();
}
Remember, you generally want to use std::unique_ptr
as a default because it's more lightweight. Additionally, std::shared_ptr
can be constructed out of a std::unique_ptr
(but not vice versa), so it's okay to start small.
Alternatively, you could use a container created to store pointers to objects, such as a boost::ptr_container
:
#include <boost/ptr_container/ptr_vector.hpp>
struct base
{
virtual ~base() {}
};
struct derived : base {};
// hold pointers, specially
typedef boost::ptr_vector<base> container;
void foo()
{
container c;
for (int i = 0; i < 100; ++i)
c.push_back(new Derived());
} // all automatically freed here
int main()
{
foo();
}
While boost::ptr_vector<T>
had obvious use in C++03, I can't speak of the relevance now because we can use std::vector<std::unique_ptr<T>>
with probably little to no comparable overhead, but this claim should be tested.
Regardless, never explicitly free things in your code. Wrap things up to make sure resource management is dealt with automatically. You should have no raw owning pointers in your code.
As a default in a game, I would probably go with std::vector<std::shared_ptr<T>>
. We expect sharing anyway, it's fast enough until profiling says otherwise, it's safe, and it's easy to use.
I am assuming following:
- You are having a vector like vector< base* >
- You are pushing the pointers to this vector after allocating the objects on heap
- You want to do a push_back of derived* pointer into this vector.
Following things come to my mind:
- Vector will not release the memory of the object pointed to by the pointer. You have to delete it itself.
- Nothing specific to vector, but the base class destructor should be virtual.
- vector< base* > and vector< derived* > are two totally different types.
The trouble with using vector<T*>
is that, whenever the vector goes out of scope unexpectedly (like when an exception is thrown), the vector cleans up after yourself, but this will only free the memory it manages for holding the pointer, not the memory you allocated for what the pointers are referring to. So GMan's delete_pointed_to
function is of limited value, as it only works when nothing goes wrong.
What you need to do is to use a smart pointer:
vector< std::tr1::shared_ptr<Enemy> > Enemies;
(If your std lib comes without TR1, use boost::shared_ptr
instead.) Except for very rare corner cases (circular references) this simply removes the trouble of object lifetime.
Edit: Note that GMan, in his detailed answer, mentions this, too.
One thing to be very careful is IF there are two Monster() DERIVED objects whose contents are identical in value. Suppose that you wanted to remove the DUPLICATE Monster objects from your vector (BASE class pointers to DERIVED Monster objects). If you used the standard idiom for removing duplicates (sort, unique, erase: see LINK #2], you will run into memory leak issues, and/or duplicate delete problems, possibly leading to SEGMENTATION VOIOLATIONS (I have personally seen these problems on LINUX machine).
THe problem with the std::unique() is that the duplicates in the [duplicatePosition,end) range [inclusive, exclusive) at the end of the vector are undefined as ?. What can happen is that those undefined ((?) items might be extra duplicate or a missing duplicate.
The problem is that std::unique() isn't geared to handle a vector of pointers properly. The reason is that std::unique copies uniques from the end of the vector "down" toward the beginning of the vector. For a vector of plain objects this invokes the COPY CTOR, and if the COPY CTOR is written properly, there is no problem of memory leaks. But when its a vector of pointers, there is no COPY CTOR other than "bitwise copy", and so the pointer itself is simply copied.
THere are ways to solve these memory leak other than using a smart pointer. One way to write your own slightly modified version of std::unique() as "your_company::unique()". The basic trick is that instead of copying an element, you would swap two elements. And you would have to be sure that the instead of comparing two pointers, you call BinaryPredicate that follows the two pointers to the object themselves, and compare the contents of those two "Monster" derived objects.
1) @SEE_ALSO: http://www.cplusplus.com/reference/algorithm/unique/
2) @SEE_ALSO: What's the most efficient way to erase duplicates and sort a vector?
2nd link is excellently written, and will work for a std::vector but has memory leaks, duplicate frees (sometimes resulting in SEGMENTATION violations) for a std::vector
3) @SEE_ALSO: valgrind(1). THis "memory leak" tool on LINUX is amazing in what it can find! I HIGHLY recommend using it!
I hope to post a nice version of "my_company::unique()" in a future post. Right now, its not perfect, because I want the 3-arg version having BinaryPredicate to work seamlessly for either a function pointer or a FUNCTOR, and I'm having some problems handling both properly. IF I cannot solve those problems, I'll post what I have, and let the community have a go at improving on what I have done so far.
'development' 카테고리의 다른 글
패턴 후 내용을 grep하는 방법은 무엇입니까? (0) | 2020.11.13 |
---|---|
Android Studio가 시작되지 않고 구성 요소가 설치되지 않았다는 메시지가 표시되지 않습니다. (0) | 2020.11.13 |
LINQ : 두 시퀀스에 정확히 동일한 요소가 있는지 확인 (0) | 2020.11.13 |
기본 클래스 포인터는 파생 클래스 개체를 가리킬 수 있습니다. (0) | 2020.11.13 |
MYSQL-데이터베이스 선택 (0) | 2020.11.13 |