1

I'd like to use the map for key/member search, the problem is that the class contains a member of buffer allocated by the malloc call.

struct Key {
    int key1;
    int key2;
    bool operator < (Key & a) { .... } 
};

struct Entity {
    unsigned char *data; 
};

typedef std::map(Key, Entity) TMap;

Then I can insert data with key like:

Tmap map;
Key key;
Entity ent;
ent.data = malloc(size);
key.key1 = xx;
key.key2 = xx;

map.insert( ...(key,ent));

The problem is that I'd like the "ent.data" pointer automatically freed when map is deleted. Also at the same time, I want I can access the "data" when do a map find operation to read the buffered data.

I tried to add destructor to the struct Entity, but it seems it leads to some duplicated free issue.

What's the solution?

[Solution]: 1. use shared_ptr:

typedef std::tr1:shared_ptr(unsigned char) CharPtr;

struct Entity {
  CharPtr data;
}

ent.data = CharPtr((unsigned char*)malloc(size), free);

Or another solution. Use Vector as Eugene Homyakov mentioned.

Sam Liao
  • 43,637
  • 15
  • 53
  • 61
  • 1
    The `std::map` typedef you've posted is invalid, have you ever used templates before? – Praetorian Aug 14 '11 at 06:34
  • @Praetorian You cache me, I'm more a c programmer. I googled, but so far did not see much good ideas about this issue. – Sam Liao Aug 14 '11 at 06:45
  • @arsane: for better solutions u need to provide more info. like, is an entity ever removed from the map before the map is destroyed. such a constraint makes possible solutions that otherwise are not on. – Cheers and hth. - Alf Aug 14 '11 at 07:18

4 Answers4

6
  1. You should be using new and not malloc in c++. And
  2. You should use Smart pointers instead of raw pointers/naked pointers inside your container.

STL containers do not take the responsibility of deallocating an element which is allocated on the freestore. Which means you would have to deallocate them explicitly. The best way to achieve this is using smart pointers, Using smart pointers you do no have to free the dynamic memory explicitly but the elements themselves take care of deallocating its own resources.

The RAII/SBRM feature of C++ was explicitly designed for avoiding problems such as the one you are facing.

Community
  • 1
  • 1
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • I gather the Anonymous downvoter, won't enlighten with what S/He feels is wrong in this answer/advice. – Alok Save Aug 14 '11 at 06:41
  • I thought to write a explict function to travel through the map to free all data, this should work. But I'm wondering if there is better solution. – Sam Liao Aug 14 '11 at 06:44
  • @arsane: Ofcourse, You can do that, but why take the trouble & the overhead when the language provides you with much better alternative(i.e. mentioned above) – Alok Save Aug 14 '11 at 06:47
  • 1
    With 2 downvotes on this answer, I would really want to know What is wrong in it, Can anyone explain? An downvote with no reason teaches no one anything, Why waste it when you can learn through it? – Alok Save Aug 14 '11 at 06:49
  • Interesting, 2 upvotes and 2 downvotes for an answer that contains good advice. +1 from me :-) – Praetorian Aug 14 '11 at 06:49
  • 1
    Downvoted, because the answer doesn't solve his problem and is a bit misleading: there is nothing wrong with using `malloc` in C++ for raw data buffers, and a smart pointer won't solve anything here while OP is still using manually allocated memory. – hamstergene Aug 14 '11 at 06:56
  • 1
    @Eugene Homyakov: It tells **How it should be done the right way**. My answers here just do not point to a quick fix but they always point out the basic flaws & the above is correct in every possible way. Glad you atleast felt inclined to leave a comment, hope you follow that practice otherwise we feel a sour kid on rampage downvoting here. – Alok Save Aug 14 '11 at 07:02
  • 3
    @Eugene Homyakov `a smart pointer won't solve anything here while OP is still using manually allocated memory` - this is wrong. You can manually allocate memory using `malloc` and stick it into a `unique_ptr` or a `shared_ptr` as long as you define a customer deleter that calls `free` in both cases. – Praetorian Aug 14 '11 at 07:03
  • @Foo Bah I have posted an answer – Praetorian Aug 14 '11 at 07:13
  • @Foo Bah: No, I won't unless the OP writes some code using the right way of doing it(mentioned above) and then gets stuck on some part which S/He does not understand. I would be glad to help in that case. I believe `Give a man a fish and he will eat for a day. Teach a man to fish and he will eat for a lifetime.` – Alok Save Aug 14 '11 at 07:13
  • @Als Thanks, I will try both your solution and vector way. – Sam Liao Aug 14 '11 at 07:20
  • 1
    Not downvoting, but not really liking this answer either. Saying "use smart pointers" is too generic. If you have shared_ptr available, that's a good choice (if you define an appropriate deleter), but not every C++ implementation has it available (http://stackoverflow.com/questions/2918202/where-is-shared-ptr). Using auto_ptr would be a terrible choice (and shouldn't compile). Both are smart pointers. arsane's alternative of explicitly deleting will work with every C++ implementation out there that supports the STL (if done right); that makes it a good choice in some circumstances. – dewtell Aug 14 '11 at 08:01
3

Classic approach would be to implement copy constructor, assignment operator and destructor in Entity. It is also recommended to put allocation code inside Entity, too.

However, there is much simpler solution here: use vector<unsigned char> instead of dynamically allocated memory:

struct Entity { std::vector<unsigned char> data; }
  • You can access the memory via &data[0]
  • It is guaranteed that vector's elements are linear in memory
  • It will be automatically deleted when Entity is deleted
  • It will be properly copied when Entity is copied
  • You can “allocate” and “reallocate” simply by calling data.resize(newsize)

Also you don't even need the insert:

Key key;
// initialize key
Entity& ent = map[key]; // automatically creates Entity for the key
// work with ent now 
ent.data.resize(bufferSize);
unsigned char* dataPtr = &ent.data[0];
hamstergene
  • 24,039
  • 5
  • 57
  • 72
  • 1
    @arsane It's *insert*, not inert – Praetorian Aug 14 '11 at 07:07
  • I have updated my answer. You can work with the inserted copy after the insertion (you don't even need to insert). There will be no other copies unless you remove that key from the map. – hamstergene Aug 14 '11 at 07:10
1

Entity needs to contain a member that indicates the size of the data was allocated. It also needs a destructor and a copy constructor. It'd be best if you added a couple of member functions to allocate and free data.

struct Entity {
  Entity() : data_(NULL), length_(0) {}

  Entity( size_t length ) : data_(NULL), length_(0)
  { 
    allocate( length );
  }

  Entity( const Entity& other )
  {
    free();
    allocate( other.length );
    std::copy( other.data_, other.data_ + other.length_, data_ );
  }

  void allocate( size_t length )
  {
    free();
    data = malloc( length );
    if( data != NULL ) {
      length_ = length;
    }
  }

  void free()
  {
    if( length_ != 0 ) { 
      free( data_ ); 
    }
  }

  ~Entity() { free(); }

private:
  unsigned char *data_,
  size_t length_;
};

typdef std::map<struct Key, struct Entity> TMap;

You'll probably want to also provide accessor functions to retrieve a pointer to the data and length of Entity.

Finally, since you're using C++ you shouldn't be using raw pointers. Consider using std::unique_ptr instead. Also, it is preferable to use new and delete instead of malloc and free.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • As for my understanding, this impl addes overhead of memory copy which in my situation is not good for performance. – Sam Liao Aug 14 '11 at 06:50
  • @arsane Most containers in the standard library require objects to be copy or move constructible. If you're using a compiler that supports move semantics you can define a move constructor instead of a copy constructor. Then you won't have to worry about copying overhead. – Praetorian Aug 14 '11 at 06:56
  • @arsane Also, you may not be aware of this but the `Entity` definition you've posted contains a copy constructor (it is implicitly defined by the compiler). However, that copy constructor performs *shallow* copying, meaning the data pointer is simply copied by value to the new `Entity` instance, without duplicating the data. This is probably the reason you were getting double deletion errors when you defined a destructor. – Praetorian Aug 14 '11 at 06:59
  • I understand what you mean for duplicated free, the problem here is that I do not want copy.. It seems that I can only try other lib instead stl, or write some a explict free-travelsal method. – Sam Liao Aug 14 '11 at 07:04
  • @arsane Have you tried whatever you're trying to do with copying included? Or have you just made up your mind that copying is too expensive for you? What kind of data lengths are you allocating anyway? – Praetorian Aug 14 '11 at 07:08
1

One simple solution is replace your malloc-ed pointer to array of unsigned char, with a std::vector<unsigned char>. Voilá, it just works.

PS1: Or, seeing as you're apparently providing your own comparision, it works if you define good comparision operator.

PS2: If you absolutely want to use malloc then you can define a custom allocator and use that with std::vector; it's a template parameter.

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • That about the comparision operator is just because it seems to be very common error. It has nothing to do with the use of `vector`, just an additional observation. Just to be clear about it. – Cheers and hth. - Alf Aug 14 '11 at 06:58