반응형

C++에서 map<key, value>를 쓰다 보면, value를 포인터로 관리해야 하는 경우가 있다. 예를 들어, 아래와 같은 형태의 코드이다.


#define <map>

using namespace std;


class Foo {

public:

    int a;

    double b;

    Foo(int a, double b){

        this->a = a;

        this->b = b;

    }

}


int main() {

    std::map<int, Foo*> fooMap;

    fooMap[101] = new Foo(10, 505.26);

    fooMap[104] = new Foo(8, 1233.61);

    // ...



    fooMap.erase(101);

    fooMap[105] = new Foo(17, 564.98);

    // ...


    fooMap.clear();

    return 0;

}


위와 같이 실행하면 메모리 누수가 발생한다. map.erase(key)를 이용해서 특정한 pair를 삭제하더라도 포인터를 통해 할당되어 있는 메모리 영역은 자동으로 해제(delete)되지 않는다. 즉, 명시적으로 코드 어디선가 new를 통해 오브젝트를 할당했으면, map의 value로 쓰이는지 여부에 관계 없이 명시적으로 delete를 해 주어야 한다.


실제로 C++의 erase와 clear 함수의 소스코드 위에 달려 있는 주석에도 같은 내용이 명시적으로 쓰여져 있다.


map erase 함수 주석:

  /**
   *  @brief Erases elements according to the provided key.
   *  @param  __x  Key of element to be erased.
   *  @return  The number of elements erased.
   *
   *  This function erases all the elements located by the given key from
   *  a %map.
   *  Note that this function only erases the element, and that if
   *  the element is itself a pointer, the pointed-to memory is not touched
   *  in any way.  Managing the pointer is the user's responsibility.
   */

...


map clear 함수 주석:

  /**
   *  Erases all elements in a %map.  Note that this function only
   *  erases the elements, and that if the elements themselves are
   *  pointers, the pointed-to memory is not touched in any way.
   *  Managing the pointer is the user's responsibility.
   */

...



포인터를 명시적으로 delete하는 것이 번거롭다면 C++11 이후의 버전들이 smart pointer를 제공하는데, (또는 이전 버전의 경우 boost::shared_ptr) 이것을 사용하면 자동으로 메모리 할당을 해제해 주므로 명시적인 delete를 할 필요가 없다.


포인터에 대해서 더이상 참조당하고 있지 않을 경우에 자동으로 메모리를 해제하는 작업은 ns-3에도 Ptr<T>로 구현되어 있는데, std::shared_ptr, boost::shared_ptr은 아직 써본 적이 없어서 자세히 모르겠지만 아마 개념과 목적은 유사할 것으로 예상된다.

(나중에 사용한 뒤에 포스트를 수정할 예정)




반응형
블로그 이미지

Bryan_

,
반응형

Java에서 사용자가 정의한 클래스를 java.util.HashMap에서 key 값으로 사용하도록 만들 때, Overriding 메쏘드 중에서 hashCode 메쏘드를 제대로 구현해야 문제없이 작동한다. 사용자 정의 클래스의 몇몇 멤버변수 값들이 다른데도 불구하고 hashCode를 제대로 구현하지 않으면 차이가 없는 것으로 판단할 수도 있기 때문이다.


C++에서는 map<const Key, T>이 비슷한 역할을 하는데, Java처럼 hashCode 함수 대신 operator < 함수를 써서 키값을 비교하는 것으로 생각된다. 

처음에는 operator = 함수만 신경써서 만들고, 실수로 operator < 함수의 내부 구현을 대충 했더니, map의 key로 서로 다른 멤버변수 값을 갖는 클래스를 대입했는데 모두 같은 value를 리턴하는 것이었다.


내 경우에는 네트워크 상의 트래픽을 표현하기 위해 Flow라는 클래스를 아래와 같이 정의했다.


class Flow {

public:

std::string srcIp;

int srcPort;

std::string dstIp;

int dstPort;

int type;

Flow();

~Flow();

};


그리고 실수로 "operator <" 함수를 아래와 같이 간단하게 만들었더니,


bool Flow::operator <(const Flow& ref) const {

return (this->srcPort + this->dstPort) < (ref.getSrcPort() + ref.getDstPort()));

}


source 또는 destination IP 주소가 서로 다른 여러 개의 flow가 모두 같은 key 값을 갖게 되는 문제가 발생했다.


그래서 std::string 형식의 IP 주소에서 숫자를 빼내서 이것들도 모두 더하는 방식으로 operator <를 구현해서 key 값이 서로 달라지도록 만들었다.


bool Flow::operator <(const Flow& ref) const {

return ((convert(this->srcIp) + this->srcPort + convert(this->dstIp) + this->dstPort) < (convert(ref.getSrcIp()) + ref.getSrcPort() + convert(ref.getDstIp()) + ref.getDstPort()));

}


이런 부분에서의 실수는 컴파일 에러에서 전혀 잡히지 않고, 가끔은 Segmentation fault 같은 에러조차 발생시키지 않고서 정상적이면서 의미상으로만 이상하게 작동하기 때문에 앞으로는 더 주의해야 할 것이다.



반응형
블로그 이미지

Bryan_

,