General question about RAII

Mar 14, 2010 at 5:52pm
Hey,

I have a general question about the RAII idiom. Although reading a lot of articles and chapters in books, I'm still unable to implement it, because I'm unable to translate the standard pattern to my specific problem. I don't even know why :), I just sit there and don't know what to do. Maybe RAII is not even appropriate to my case. My problem:

I have a class CLASS1. One of it's private members is a pointer to a different class CLASS2.

1
2
3
4
5
6
7
8
9
10
11
class CLASS1 {
public:
        CLASS1();
        ~CLASS1();
private:
        int i_;
        int k_;
        int l_;
        int f(int, int);
        CLASS2* p_;
};


The object to which p_ points should be allocated on the heap. The first idea was to define CLASS1() like this:

1
2
3
4
5
6
CLASS1::CLASS1() {
        i_ = 0;
        k_ = 1;
        p_ = new CLASS2();
        l_ = f(k_, i_);
}


with the following destructor:

1
2
3
CLASS1::~CLASS1 () {
       delete p_;
}


The constructor might screw up, because of f(), so the destructor might never get called. Can I circumvent the problem by allocating p_ at the end? Is this good style?

The CLASS2 constructor does likewise just assignments and some internal computation:

1
2
3
4
5
6
CLASS2::CLASS2 () {
        name_ = "Hello";
        b_ = 0;
        c_ = 1;
        a_ = b_ + c_;
}


The other idea was to write a wrapper class for CLASS2. Well, I don't know what really to do here. Probably something like this?:

1
2
3
4
5
6
7
8
9
10
11
12
13
class CLASS2W {
public:
        CLASS2W() {
                cl_ = new CLASS2();
        }

        ~CLASS2W() {
                delete cl_;
        }

private:
        CLASS2* cl_;
};


... and altering the CLASS1 together with its constructors to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CLASS1 {
public:
        CLASS1(): p_();
        ~CLASS1();
private:
        int i_;
        int k_;
        int l_;
        int f(int, int);
        CLASS2W* p_;
};

CLASS1::CLASS1()
        : p_() { // RAII ?
        // as above (?)
}


Is it worth to use RAII in my case? How do I do it properly?

Thx in advance

Btw: What does the ":" in the constructor really mean?
Mar 14, 2010 at 6:10pm
The constructor might screw up, because of f(), so the destructor might never get called.


The only way the destructor won't be called is if:

1) You throw an exception from f
or
2) f does something ugly, like call exit (which you shouldn't do for this very reason)

Exceptions can be caught:

1
2
3
4
5
6
7
8
9
10
11
12
13
CLASS1::CLASS1(){
        i_ = 0;
        k_ = 1;
        p_ = new CLASS2();
        try
        {
                l_ = f(k_, i_);
        }catch(...)
        {
                delete p_;
                throw;  // rethrow the exception
        }
}


Or, if f() doesn't need p_, you can just allocate your object after you call f.

The CLASS2W idea has the same problem. If the ctor exits abnormally, the dtor won't be called, so I wouldn't go with that route.

EDIT:
Btw: What does the ":" in the constructor really mean?


It's the initializer list. It calls the ctors of your members, whereas the body of the constructor runs after all of the members have been constructed.
Last edited on Mar 14, 2010 at 6:11pm
Mar 14, 2010 at 7:11pm
Thanks for the reply,

I wanted to avoid try() - catch() statements. It is probably more that sufficient in my case but I wanted to understand RAII and how to implement it properly.

The CLASS2W idea has the same problem. If the ctor exits abnormally, the dtor won't be called, so I wouldn't go with that route.


Which ctor do you mean? The CLASS2W ctor makes it possible to bind the new-delete to the ctor-dtor pattern of a local object (i.e. CLASS2W), thereby making sure that whenever CLASS1::p_ goes out of scope, the allocated memory on the heap will be released by the CLASS2W dtor. The CLASS1 ctor can't throw an exception before the CLASS2W ctor finished successfully and the C++ standard ensures that the destructor of a successfully constructed object will be called. Therefore, if the CLASS1 ctor throws an exception the allocated memory on the heap will be released by the CLASS2W dtor, or do I miss something? I thought this is the standard way of implementing RAII, which should exactly avoid the problem you mentioned.

I'm still a beginner when it comes to C++ and want to make sure I understand RAII correctly in order to avoid any memory leaks. So how do I have to implement this correctly, if I don't want to use the try-catch method?
Mar 14, 2010 at 7:40pm
The CLASS2W ctor makes it possible to bind the new-delete to the ctor-dtor pattern of a local object (i.e. CLASS2W), thereby making sure that whenever CLASS1::p_ goes out of scope, the allocated memory on the heap will be released by the CLASS2W dtor


Not necessarily. Since the CLASS2W object is a member of CLASS1, CLASS2W's dtor get called by CLASS1's dtor, so if CLASS1's dtor isn't called, neither is CLASS2W's

It CLASS2W was local, that's another story. But then it won't be a member.

Anyway this isn't really an RAII issue, this is more of an exception safety issue. I'm sure you could work in some kind of RAII solution to the problem, but it would end up being a lot more complicated and cumbersom than a try block.

So how do I have to implement this correctly, if I don't want to use the try-catch method?


Here's the only way I can think of (but I don't recommend it because it's a little ridiculous)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class C2 { /* whatever*/ };

class C2R
{
public:
  C2* p;
  
  C2R() : p( new C2 ) { }
  ~C2() { delete p; }
};

class C1
{
public:
  C1()
  {
    C2R raiihack;
    ptr = raiihack.p;

    AFunctionThatMightThrowAnException();

    raiihack.p = 0;  // prevent C2R's dtor from deleting the object
  }

  ~C1()
  {
    delete ptr;
  }

private:
  C2* ptr;
};


But again, this is a poor design, and you shouldn't do it. Just use a try block.
Mar 14, 2010 at 10:17pm
Not necessarily. Since the CLASS2W object is a member of CLASS1, CLASS2W's dtor get called by CLASS1's dtor, so if CLASS1's dtor isn't called, neither is CLASS2W's

Hm, that's curious. Stroustrup says in § 14.4.1 in [C++ Programming Language, Special Edition], that in a class like

1
2
3
4
5
6
7
class Z {
        vector<int> p;
        void init();
public:
        Z(int s) : p(s) { init(); }
        //...
};


if init() throws an exception, the memory allocated by p will be released by implicitly calling the dtor for p. The dtor for p is the dtor of the vector-class, as I understand it, because he also states that the allocated memory of p is now managed by vector. Doesn't that also apply to my CLASS2W* p_?

It CLASS2W was local, that's another story. But then it won't be a member.

I thought CLASS2W was local. As I understand it, CLASS2W doesn't get allocated on the heap. I think it gets allocated on the stack, containing a pointer of type CLASS2 that points on the heap.

Anyway this isn't really an RAII issue, this is more of an exception safety issue.

Actually, no, because all I want to is allocating a class on the heap an making sure it get's deleted no matter what. The try-catch statement is one way but Stroustrup states in the above mentioned book that such a solution is rather bulky and costly. The reason why I want to do this in the RAII manner is, because the classes might get bigger and more complex and then you have to be very aware of where to try-catch. I want to be able to not think about this and just use the resources I need without explicitly doing memory management. And Stroustrup says that's the way to go. However, I'm not sure about my approach. How do I test if I have a memory leak anyway?

Regarding you code:
I don't understand why C1::ptr is of type C2* (line 31); raiihack.p doesn't point on the stack, does it? But that's what I'd like to accomplish. Confusing this is :).
Mar 14, 2010 at 11:17pm
!

Actually, I think you're right. I had it all wrong!

Many apologies. I don't know what I was on, but yeah. I was totally wrong. CLASS1's dtor wouldn't be called -- but all the member dtors would be. I guess I just got those mixed up.

Let's try this again

With this class:
1
2
3
4
5
6
7
8
9
10
11
12
13
class CLASS2W {
public:
        CLASS2W() {
                cl_ = new CLASS2();
        }

        ~CLASS2W() {
                delete cl_;
        }

private:
        CLASS2* cl_;
};


You can do this:

1
2
3
4
5
6
7
class CLASS1 {
public:
        CLASS1() { SomethingThatMightThrowAnException(); }
        ~CLASS1() { }
private:
        CLASS2W p_;  // notice, not a pointer
};


There we go.

Again -- many apologies. I really don't like to dish out false information. I'm glad you stepped up and corrected me. Thanks.
Mar 15, 2010 at 11:47am
The constructor might screw up, because of f(), so the destructor might never get called. Can I circumvent the problem by allocating p_ at the end? Is this good style?


MEEEP. Wrong way of thinking.

Not the order in the initialization list (the thing after ":") matters, but the order of declaration in the class body (sic). So for your case, p will already be assigned after f is called, just because you declare p_ after l_ in your class declaration. Be aware of any cross references in the member variables. You have to order the declaration of the members, and not the order in which you assign values. (This is a very common mistake).


Now for your question: Yes, RAII can (and should) be used here, if you expect f to throw anything (or if you care what happens should it throw). Many code designers just define something like "Should exceptions happen, then I am already paid and in Florida. Devil-may-care." ;-) (Which may be totally ok depending what code you write..)

Best form is, you just use the provided std::tr1::shared_ptr if it is already provided for your environment. This' may be a bit overkill for you (it's a fully fledged shared pointer including reference counting and everything). You may also consider std::tr1::unique_ptr or std::auto_ptr if p_ is not assigned to anything.

If you really want to implement your own RAII class wrapper, CLASS2W is a good start. You'll need some method to get to the wrapped value, though. operator*() and operator->() are common choices. The resulting construct is then called "smart pointer" (like std::tr1::shared_ptr). You may also just provide a standard-getter.

To make it work, just use a normal class (not a pointer) in CLASS1:

1
2
3
4
class CLASS1 {
...
    CLASS2W c1_;
...


As you and Disch have already figured out, any member that has been fully constructed will be destructed properly in case of exceptions ;). By the way, this is a general rule in C++ which is very holy to the creators: If any constructor finished properly, the class destructor will be called. You can rely on this, even if the class is a (wrapper)-member of another. RAII is all about this guarantee.

Ciao, Imi.
Last edited on Mar 15, 2010 at 11:50am
Mar 15, 2010 at 5:38pm
Hey imi & Disch

thanks for your replies. Both of you gave the advice to not use a pointer to a class in my CLASS1. What's the point behind this? I have to think about it now and try to implement it. I'll get back to you if I run in some trouble :). Thanks again for the help.
Mar 15, 2010 at 5:50pm
imi is right. When ever one hears "RAII", think "how can auto_ptr be used here?" If you need to allocate a member object on the heap, and there are legitimate reasons to do so, this is the preferred way to do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <memory>

class CLASS1 {
public:
        CLASS1();

private:
        int i_;
        int k_;
        int l_;
        int f(int, int);
        std::auto_ptr<CLASS2> p_;
};

CLASS1::CLASS1()
: i_(0), k_(1), l_(f(k_, i_)), p_(new CLASS2)
{}


CLASS2 is automatically cleaned up under all circumstances.
Mar 15, 2010 at 8:38pm
Both of you gave the advice to not use a pointer to a class in my CLASS1. What's the point behind this?


Class member variables get constructed before the body of the constructor is executed. (That's what this "initialization list" is for - to construct the member variables). If you use a "normal class" here and not a pointer, the compiler will have this' class constructor executed to create the member variable. If now anything happens in your normal constructor, the member variable object has already been created, so C++ guarantees that it's destructor will be called - even if the own class destructor will not be called.

Hm.. sounds pretty complicated, lets illustrate it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Wrapper 
{
    int* p;
    Wrapper() : p(new int){}
    ~Wrapper() { delete p; }
};
struct MyClass
{
    Wrapper w; // not a pointer, but a "normal class"
    int i;
    MyClass()
      : w(), // this will be called before the body below executes

// at this point, w is fully constructed and C++ will guarantee, that ~w will be called even in case of exceptions.

        i( exceptionPossibleFunction() )  // this is also called after w(), as "int i" comes after "Wrapper w" above
    {
        // call any other dangerous functions that might throw exceptions as you like
    }
    ~MyClass() // This will NOT be called if anything gone wrong in MyClass::MyClass.
    {
        // do NOT free w, as it's freed by its own destructor automatically.
    }
};


Hope it was clear enough? ;-)

By the way.. If you recognize, that by using RAII for member variables, your non-wrapper main classes' destructors start to get empty: That's good! (and normal ;-) )

Note, that you also could invert the order of declarations for "Wrapper w" and "int i" and it would still work. If the possible exception raises before w gets constructed, C++ will not call any destructor of the wrapper and everything is fine ;-).

PanGalactic wrote:
When ever one hears "RAII", think "how can auto_ptr be used here?"


Well, actually I recommend against using auto_ptr as member variable. It's because of the unexpected "move value" semantic. If you assign an auto_ptr to an other, the first auto_ptr get's modified and now point to 0! This also includes member-variables that are auto_ptrs.

1
2
3
4
5
6
7
struct Foo { auto_ptr<int> p; Foo():p(new int){} };
...
Foo f;
*(f.p) = 42;
Foo f2 = f;
cout << *f2.p; // prints out "42" as expected.
cout << *f.p; // WHOOPS! segfault because now f.p points to 0 ! 


Short line: Never use auto_ptr as member variable, except the class is non-copyable (means: it has not-implemented and private copy constructor and operator=)

Ciao, Imi.
Last edited on Mar 15, 2010 at 8:39pm
Mar 15, 2010 at 11:17pm
imi wrote:
I recommend against using auto_ptr as member variable. It's because of the unexpected "move value" semantic.


Yes, one must provide the copy constructor and assignment operator when using auto_ptr, just as one would if the class contained a raw pointer. That's not a reason not to use it when heap allocation is advisable.
Mar 16, 2010 at 9:26am
Yes, one must provide the copy constructor and assignment operator when using auto_ptr, just as one would if the class contained a raw pointer. That's not a reason not to use it when heap allocation is advisable.

Maybe it depends on the people you work with or who work with your code ;). I tend to know a lot more people (including myself) who are very familar with how plain pointer work and are much more sensitive about what happens and what not if you copy and pass plain pointer around. Compared to the behaviour of std::auto_ptr, that is. ;-)

The problem is not that you have to manually define copy sematinc with auto_ptr as member. The problem is, that if you *forget*, you don't get compile errors. Instead you get default generated but unexpected behaviour of modifying the object you assigned from. This strange behaviour is not found anywhere else in C++.

Also, std::auto_ptr does not have a future. std::unique_ptr will come to the rescue! It will "replace" auto_ptr soon which will be deprecated and discouraged to use. The new unique_ptr is the same as auto_ptr, but just don't allow standard copying and assignment. But it *does* allow for the new "move constructor" to have the very interesting usage in using unique_ptr as return value of functions. All the plus with no minus. Horay! ;-)

Ciao, Imi.
Mar 16, 2010 at 11:12am
@PanGalactic:
I thought about using auto_ptr myself since it is a pure RAII class, however, I first wanted to make sure I understand the principle behind RAII better so I can use auto_ptr later on with more confidence. I will certainly look into that.

@imi:
Hope it was clear enough? ;-)

I think so, yes. Thanks for all the help. One question though: "Is there a way to test whether my code (unintentionally) produces memory leaks? I'm really not that experienced in development.
Mar 16, 2010 at 11:50am
Is there a way to test whether my code (unintentionally) produces memory leaks?

There is no reliable way of telling this with only using standard C++ constructs.

But e.g. Microsoft has some debug methods in their arsenal if you use MSVC. Easiest I stumbled upon is: just call the following as first thing in your main function:

1
2
3
4
int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
...


If you run the program in debug mode, you see a dump of all memory positions together with the first bytes of the content of these locations.

There is even a tutorial how to get to the place where this memory got allocated on the MSDN somewhere.

However, beware that this only catches memory that is never de-allocated on program termination.

Ciao, Imi.
Last edited on Mar 16, 2010 at 11:50am
Mar 16, 2010 at 7:56pm
hey imi,

unfortunately, I don't have Windows. It's too expensive :).
Mar 16, 2010 at 8:01pm
If you use Linux and GCC, google for "mtrace". IIRC this was a tool which does similar things. It's for C and tracks malloc(), but maybe it can be used for C++ as well. (new usually just forwards to malloc)

Ciao, Imi.
Topic archived. No new replies allowed.