C++ RAII Questions
Asked Answered
U

4

10

So as I understand it to implement RAII properly, if I where to call CreateFont, I'd wrap that in a class with CreateFont in the constructor and DeleteObject in the destructor, so it cleans it up when it goes out of scope.

First question is, won't I end up with ALOT of classes doing that? Especially since the class only has a constructor and destructor.

Second question is, what if I'm calling the CreateFont class in the WndProc, that goes out of scope constantly. So am I supposed to do all my calls to CreateFont or like LoadBitmap in the WndMain? I'm used to calling those functions in WM_CREATE and cleaning them up in WM_DESTROY.

Umiak answered 26/9, 2011 at 14:0 Comment(2)
Yes, you will end up with a lot of classes. That's what all Win32 API wrapper libraries end up having. There's a good reason too, which has nothing to do with RAII. The Win32 API, especially the GDI part, has lots of concepts to wrap! RAII or not, you will end up with Window, Device, Painter, Font, Bitmap, ... classes. It follows the one class, one responsibility principle. You end up with lots of easy to use classes.Matelda
You will end up with a lot of classes, but they'll all pretty much consist of just the original object you are "wrapping" and the "delete" code will likely be inlined where it is used. So yes, you end up with a lot of classes but it's not inefficient at run time, it's pretty much what you'd write yourself, only less likely to be wrong.Armet
M
10

You can avoid a lot of repetitious work by using a template to help you. For example if you use boost::shared_ptr you can do:

#include <boost/shared_ptr.hpp>
#include <functional>

struct Font;

Font *createFont();
void deleteFont(Font*);

int main() {    
  boost::shared_ptr<Font> font(createFont(), std::ptr_fun(deleteFont));
}

Which saves you writing a custom class to manage the resource. If boost and TR1 or newer aren't available to you it's still possible to implement something similar and generic yourself to assist.

boost::shared_ptr is reference counted properly, so if you want to create it somewhere and "promote" it to live longer later you can do so by copying it somewhere longer lived before it dies.

Mithgarthr answered 26/9, 2011 at 14:17 Comment(5)
The only problem with this solution is that it doesn't help wrap any of the other operations on Win32 GDI fonts. I much rather have a Font class that wraps Win32 calls and checks for errors.Matelda
@AndréCaron - I'm not familiar enough with Win32 GDI to directly answer that point, but my inclination would be to use composition with boost::shapred_ptr to make something sensibly copyable (with the default copy constructor) that hides whatever other details you want to hide.Mithgarthr
That's exactly what I would recommend. But then we're back to the "lots of classes" syndrome. See my comment to OP's question.Matelda
@AndréCaron - but that's not RAII that's causing the lots of classes problem then, rather the desire to wrap an existing API. The OP's question was about wrapping object creation/destruction with RAII, not wrapping in general. boost::shared_ptr (or similar) can do RAII without "lots of classes syndrome"Mithgarthr
That's why I didn't post an answer and preferred to comment. This totally answers the question at hand. My comment is intended for OP, such that it might still be useful (necessary?) to write lots of classes anyways.Matelda
S
4

First question is, won't I end up with ALOT of classes doing that? Especially since the class only has a constructor and destructor.

Yes, but there are few points to consider:

  • is it a problem? The classes will be small and easy to read and understand,
  • you might be able to reuse many of them (for example, there are a lot of Win32 functions which create HANDLE objects, and they're all closed the same way (with CloseHandle), so you could reuse the same class for those.
  • you can use a smart pointer or some other generic wrapper to fill in most of the boilerplate code. The most popular smart pointer classes allow you to specify a custom deleter function.

Second question is, what if I'm calling the CreateFont class in the WndProc, that goes out of scope constantly.

Store it in a location where it won't go out of scope prematurely. :) Here, smart pointers might be useful again. For example, shared_ptr could be used to keep the font alive as long as there's at least one shared_ptr pointing to it. Then you can simply pass it out of the function to some common longer-lived location.

Otherwise, as long as you implement copy constructor and assignment operator (in C++11 you might want to implement move constructor and move assignment instead) for your RAII class, it can be copied (or moved) safely to wherever you want to put it, even if it was created in a smaller scope.

Stressful answered 26/9, 2011 at 14:25 Comment(0)
P
1

First question is, won't I end up with ALOT of classes doing that? Especialy since the class only has a constructor and deconstructor

If you don't like the number of classes you'd need to create for each different type of object, you can create a single RAII class that takes a HGDIOBJ parameter in the constructor and calls DeleteObject in the destructor. This class can then be used for all the different GDI objects. For example:

class GDIObject
{
public:
    HGDIOBJ GdiObject;

    GDIObject( HGDIOBJ object )
        : GdiObject( object )
    {
    }

    ~GDIObject()
    {
        DeleteObject( GdiObject );
    }
}

...

GDIObject font( CreateFont( 48, 0, 0, 0, FW_DONTCARE, false, true, false, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Impact") ) );

Second question is, what if I'm calling the CreateFont class in the WndProc, that goes out of scope constantly. So am i supposed to do all my calls to CreateFont or like LoadBitmap in the WndMain? I'm used to calling those functions in WM_CREATE and cleaning them up in WM_DESTROY.

For items that need to remain in-memory for longer than the function scope, you'll have to put these at the global level.

Package answered 26/9, 2011 at 14:19 Comment(3)
There are better options than putting RAII objects at the global level. One that comes to mind is: don't use RAII for resources that are not to be released at the end of scope.Matelda
The whole point of using RAII is for automatic resource handling. If global items aren't handled with RAII, then the application has to manually release the items.Package
Exactly. But global objects that manage resources are simply evil. There is some manual work involved in cleanup of such global objects anyways.Matelda
A
0

If you're going for solid RAII, there are some options for reducing the number of classes you'll need - for example, some boost smart pointers (shared_ptr specifically) allow you to provide your own function to call when the pointer goes out of scope.

Otherwise, yes - you'll have a class for any resource which requires explicit freeing - and while it's a pain in the butt code-wise, it will save you massive time in debugging in the long run (especially in group projects). I'd still say to use RAII based on your own feelings on the situation though - it's a wonderful idea to use it everywhere, but some times when converting old code it can be a massive amount of work to fix all the call-chains up to use it.

One more thing - I'm not overly familiar with the GUI logic you're working with, so I won't go there specifically... but you'll have to look into how your resources should be copied and how long they should be maintained. Will your RAII containers be reference counting, or will they have value (copy) semantics? Reference counting smart-pointers like shared_ptr may solve your problem about recreation of resources frequently as long as you can pass a reference to the original around your code.

Agora answered 26/9, 2011 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.