Proper C++ way to allocate a LPRGNDATA struct

Started by
6 comments, last by JohnBolton 19 years, 6 months ago
I'm learning from Andre Lamothe's books. While I enjoy his method of explanation, and mostly follow it, I detest his style of coding. (What I mean by that is the lack of C++ classes and methodology. He uses some portions of C++ but the portions used seemed to center on the conveniences that C++ provides rather than the structure. His books are books meant to teach DirectX though, not proper C++ coding. :)) When initializing a region data structure, I'm not sure how to do this "properly":

LPRGNDATA RegionData;
RegionData = (LPRGNDATA) malloc(sizeof(RGNDATAHEADER) + (p_RectangleCount * sizeof(RECT)));
memcpy(RegionData->Buffer, p_ClipList, (sizeof(RECT) * p_RectangleCount));

// Set up the header and member fields.
RegionData->rdh.dwSize          = sizeof(RGNDATAHEADER);
RegionData->rdh.iType           = RDH_RECTANGLES;
RegionData->rdh.nCount          = p_RectangleCount;	
RegionData->rdh.nRgnSize        = p_RectangleCount * sizeof(RECT);
	
RegionData->rdh.rcBound.left    =  64000;
RegionData->rdh.rcBound.top     =  64000;
RegionData->rdh.rcBound.right   = -64000;
RegionData->rdh.rcBound.bottom  = -64000;

return RegionData;


Would the best way to do this be to create a new LPRGNDATA struct and initialize RegionData->Buffer to a RECT array as needed? I just want to avoid malloc and free usage if I can. Good habits start early and all that. :) Thanks in advance.
..what we do will echo throughout eternity..
Advertisement
I don't really think there is a clean way to do this in c++, unless you create a new struct with an array with a fixed number rects. If you want to have a dynamic number of rects, this is probably the best way to do it.
Normally you wouldn't do something like this this way altogether in c++, but the win32 api wants it this way, so I guess this is about the only option.

In a clean c++ way it would be something like this:

class{Header header;Rect *rects;orstd::vector<Rect> rects;};


but that won't result in the same data, so you won't be able to use that with the win32 api.

If you are going to use those structs a lot, you could create a wrapper class, but otherwise I would just do it on andre lamothes way.

If you just want to avoid malloc you can of course use new with chars, and then cast it to a LPRGNDATA, but that oviously defeats the purpose of new:

RegionData = (LPRGNDATA)new char[size];
You'll probably be better off sticking with using malloc on this one because win32 api was written in C and the definition of RGNDATA is:

typedef struct _RGNDATA {     RGNDATAHEADER rdh;     char          Buffer[1]; } RGNDATA, *PRGNDATA;


as you can see Buffer is really gonna be more than one element, a C trick, malloc & free deal with uninitialized memory where as "normal" operator new allocates memory then initializes it by invoking constructors (even for built-in primitive types) so it would be pretty use-less in this context especially because of Buffer member, the closet you can get to that is probably:

RegionData = static_cast<LPRGNDATA>(::operator new(sizeof(RGNDATAHEADER) + (p_RectangleCount * sizeof(RECT)));memcpy(RegionData->Buffer, p_ClipList, (sizeof(RECT) * p_RectangleCount));/* ... */::operator delete(RegionData);


which isn't any better just does exactly the same thing.
You CAN do this in C++, but it's somewhat crufty. You can use operator new, and placement new, together.

template< class Iter >RGNDATA * NewRegion( Iter begin, Iter end ){  size_t size = sizeof(RGNDATAHEADER)+sizeof(RECT)*(end-begin);  RGNDATA * ret = (RGNDATA *)::operator new( size );  new( ret ) RGNDATA;      // this isn't really necessary  RECT * rct = reinterpret_cast< RECT * >( ret+1 );  for( size_t i = 0; i < rgn->nCount; ++i ) {    new( &rct ) RECT;   // this isn't really necessary  }  std::copy( begin, end, rct );  // fill out the region header here  ...  return ret;}void DeleteRegion( RGNDATA * rgn ){  RECT * rct = reinterpret_cast< RECT * >( rgn+1 );  for( size_t i = 0; i < rgn->nCount; ++i ) {    rct->~RECT();   // this isn't really necessary  }  rgn->~RGNDATA();  // this isn't really necessary  ::operator delete( rgn );}


If you want a class that looks more like a value class, I can't see how you could do this, and support variable size, without getting two allocations; one for the wrapper value class, and one for the actual data layout (which could just re-use the above code).

In that case, I'd probably also use ref counting of the region data; allocate an int more data than you need, and use that int for refcount before the actual region data. Offset the pointer by sizeof(int) when you need to get an actual region pointer.

The core problem is that the region data structure is laid out to minimize heap operations (which are expensive) rather than being convenient for high-level C++.
enum Bool { True, False, FileNotFound };
Not that that is in any way more clean than the original c code. And you can wrap that c code in those functions too, just as well;).
free() and malloc() it is. *grin*

Thanks guys!
..what we do will echo throughout eternity..
If you just want to clip the display use the SetHwnd() method of IDirectDrawClipper. a lot cleaner than filling out a RGNDATA struct.
The basic difference between malloc/free and new/delete is that the constructor and destructor are called. If the RGNDATA struct has no constructor or destructor then malloc/free and new/delete do approximately the same thing.

You could derive your own RegionData class from RGNDATA just to add a constructor and destructor, but that would probably be overkill.

Either way, if I have the choice, I always use new/delete over malloc/free because it is safer and it is extendable.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!

This topic is closed to new replies.

Advertisement