Pattern name for create in constructor, delete in destructor (C++)
Asked Answered
P

3

11

Traditionally, in C++, you would create any dependencies in the constructor and delete them in the destructor.

class A
{
 public:
    A() { m_b = new B(); }
    ~A() { delete m_b; }
 private:
    B* m_b;
};

This technique/pattern of resource acquisition, does it have a common name?
I'm quite sure I've read it somewhere but can't find it now.

Edit:
As many has pointed out, this class is incomplete and should really implement a copy constructor and assignment operator.
Originally, I intentionally left it out since it wasn't relevant to the actual question: the name of the pattern. However, for completeness and to encourage good practices, the accepted answer is what it is.

Polyvinyl answered 4/12, 2009 at 10:36 Comment(4)
Bad class A. Copy constructor and Assignment operator are missing.Nerland
A simpler solution if copy/assignment isn't required is to derive from boost::noncopyable. Saves you having to define those two functions. (and ensures they're not accidentally called)Courses
What a pointless usage of dynamic memory. What a terrible design and formatting of the class. What a terrible example of C++ overall. Why would you ever do that? Why would you ever use new and delete anyway, and in such a pointless way. Just do class A{ B m_b = {}; } and you get pretty much the same but without risky pointers and using less than 1/4th the code.Tellurate
You don't need to use private: because the entire point of the class keyword is to define members private as default, as they're meant to be on top of the class, and not hidden at the bottom in a place you need to scroll down the entire page to see them. In my 30 years of programming I have never seen a single argument to put the members at the bottom and the language is clearly meant to have them at the top.Tellurate
N
14

The answer to your question is RAII (Resource Acquisition Is Initialization).

But your example is dangerous:

Solution 1 use a smart pointer:

class A
{
  public:
     A(): m_b(new B) {}
  private:
     boost::shared_ptr<B> m_b;
};

Solution 2: Remember the rule of 4:
If your class contains an "Owned RAW pointer" then you need to override all the compiler generated methods.

class A
{
  public:
     A():              m_b(new B)           {}
     A(A const& copy): m_b(new B(copy.m_b)) {}
     A& operator=(A const& copy)
     {
         A  tmp(copy);
         swap(tmp);
         return *this;
     }
    ~A()
     {
         delete m_b;
     }
     void swap(A& dst) throw ()
     {
         using std::swap;
         swap(m_b, dst.m_b);
     }
  private:
     B* m_b;
};

I use the term "Owned RAW Pointer" above as it is the simplest example. But RAII is applicable to all resources and when your object contains a resource that you need to manage ('Owned RAW Poiner', DB Handle etc).

Nerland answered 4/12, 2009 at 11:32 Comment(28)
Why is his example leaky? He allocated on constructor and deallocates on destructorSpermaceti
Because of the compiler generated copy constructor.Nerland
"owns a raw pointer" should probably be expanded to "or a resource handle of any other kind". The same applies for file handles, sockets, database connections or any other resource whose lifetime has to be managed.Courses
Btw, this really shows the power of "the fastest gun in the west" syndrome. This answer is by far more complete and detailed, but because the other one is at the top, it's getting all the upvotes...Courses
RAII: "Resource Acquisition Is Initialization"Whalebone
@Courses it may also be that the code shown is quite disgusting and something you should not do without a good explanation.Tellurate
@PabloAriel code shown is quite disgusting In what way?Nerland
In that you're using dynamic memory for no reason, you put the most important part of the class at the bottom (the member it contains, which tells what the class is meant to manage and thus its reason to exist), in that you use a raw pointer, in that it makes an extra copy of the value of A const &copy, in that it uses using for "using" the function only once, in that it uses four lines for a one-line destructor... as I mentioned, this class can be written as class A{ B m_b = {}; } and it should unless there is a good explanation to do it some other way.Tellurate
There is a myriad of good examples of RAII, such as lock guards, COM+ smart pointers, smart pointers in general (tho the std:: implementations are questionable at the very least), file mappings and so on. There is no reason, however, to do something like in your example, wrapping new() and delete() for no reason whatsoever. Not a real-world example and dangerous to imitate.Tellurate
@PabloAriel: Thanks for the input. I agree with Not a real-world example. But details needed to be given to adequately answer the original question. I personally disagree with all your other points.Nerland
@PabloAriel Given the original question a copy needs to be a true copy. Thus neither std::unique_ptr or std::shared_ptr would actually work (without additional code). unique_ptr would simply prevent the copy and shared_ptr does not give you a new copy but shared data. Now I can see using B m_b is definitely better than using B* m_b in most cases but the OP has chosen an implementation that uses a pointer for a reason (otherwise the simpler solution would have been used). So saying class A{B m_b={};} is not an answer.Nerland
@PabloAriel Understanding how to wrap new/delete is a good idea for all programers. Though best advice is still with using std::"smart pointer" as suggested with the first example.Nerland
@PabloAriel Based on your comments I have the feeling you have also not discovered the Copy and Swap idiom I suggest you do some research on that.Nerland
@PabloAriel Based on your comments I have the feeling you don't understand how to write a standard swap() method. You should probably learn the idiomatic way to write swap.Nerland
I do know about the copy and swap idiom. I also know how to avoid copying data redundantly and unnecessary memory allocations. I don't think there is ever the need to use "new" in a constructor nowadays, but you're free to provide a real-world example if you think there are times in which it's unavoidable.Tellurate
@PabloAriel What about std::vector<> I bet it has a new in its constructor :-) . Basically any time you are creating a container or a smart pointer these are necessary. Just because the standard library has the most common ones does not mean you will never come across situations where the standard ones are not sufficient (so you should know how to do it when you need to). Though admittedly those will be very rare. Everybody should try and create a container/smart pointer (not just for the learning experience, but also to understand how hard they are to do correctly).Nerland
@PabloAriel Now I will be the first to say that in modern C++ you should not be using new/delete (especially when talking to beginners). But when you are in real life and have the experience these things do come up and you should know how to use them. The statement I don't think there is ever the need to use "new" in a constructor nowadays is just wrong. Sure it is rare but not understanding how to do it when you are experienced is an issue. Don't do it when you inexperienced but real life is a lot more complicated and thus experienced user will need to know this.Nerland
Ok, they may need to know it exists and that people do such things, but also they need to know it's not the way they should do it. Even something like std::vector shouldn't have any allocations in its default constructor, only in resize() operations such as copy constructors, but that's a different kind of constructor. Even then, I use malloc() and placement new in order to allocate memory larger than the one requested for faster push(), and there are no matching deletes for such news.Tellurate
@PabloAriel You need to study more if you are using malloc()!!!! (thats a memory allocation system from a completely different language). Yes std::vector<> needs allocation in the default constructor and a deallocation in the destructor!!!!! Please read more about RAII !!!!! But now you are also changing the discussion to the allocation of the elements in the container, sure they need the use of placement new and also require you to manually call the destructor. BUT that has nothing to do with this discussion. Why are you trying to build a straw man?Nerland
but also they need to know it's not the way they should do it. Thats a very overall broad statement. You need to know how to do it because every now and then you will need to do it. Now most of the time you will not need to do it (and when you do I would expect my developers to justify it). But the real life is not as simple as you seem to think it is things are not as simple and cut and dried as you seem to think. You need to know how to do it because it is something you will use.Nerland
I never saw a situation in which you need to do it and I have plenty of developments as example, from web services to 3d games which are available on github. Also, malloc() is still part of C++ (en.cppreference.com/w/cpp/memory/c/malloc), and you need to use it in order to take advantage of placement new which enable important optimizations, though I don't even use that as I have a wrapper for enforcing aligned malloc in the different platforms. My code contains custom smart pointers and std::vector-like classes but the default constructor only initializes members to zero/null.Tellurate
@PabloAriel You don't need to use it to use placement new. You can use the C++ allocators. malloc is part of C. Read the page you linked: It even uses a potentially different memory allocation area. It just happens that there are bindings from C++ to the C language (like there are bindings from nearly every language to C).Nerland
@PabloAriel Sorry this is a classic argument from incredulity: I never saw a situation in which you need to do it . I have been doing this a long time. RAII is the primary mechanism that C++ developers need to learn. I have written several articles on how to do this correctly: See: lokiastari.com/series the section of vectors and smart pointers where I walk through doing them correctly. You should definitely learn how to do it. This is a skill you will need if you want to continue as a C++ developer.Nerland
I know how to do RAII. I use it when it makes sense, such as for mutex locks and file handles. Nothing that you would ever new() in the default constructor, however. My framework includes its own smart pointer implementations and other raii facilities and if you have any critics to it they will be more than welcome. Also, using C headers leads to faster builds than compiling C++ template files.Tellurate
@PabloAriel RAII Is for anything where there is create/doSomthing/destory pattern. They are basically the standard idiom in C++ and used for everything. If you are not writing destructors then you are probably doing something wrong. C headers are not great because they are inconsistent on if they put things in the std namespace. C header files => always global namespace (sometimes std namespace). C++ header files => always std namespace. If you want to swap speed at compile time for safety that's a false saving as the cost of debugging the resulting issues will be orders of magnitude largerNerland
PS. Why would you use RAII for mutex locks std::lock_guard and files std::fstream but not for memory std::vector, when all of these are already in standard? Is there something magical about memory and not other resources?Nerland
I think none of those classes do new() in their default constructor and they shouldn't if they want to avoid plenty of unnecessary allocations, but you're free to look for the code and share it so everyone can see how I'm wrong. ie. with a real world example.Tellurate
No worries about the debugging. It is solved by using my own implementations of things like std::vector. I never use naked arrays or raw pointers.Tellurate
I
20

RAII - Resource Acquisition Is Initialization

Inhabiter answered 4/12, 2009 at 10:38 Comment(2)
though typically you'd use a smart pointer to do the RAII for you rather than code it yourselfGlennaglennie
Not necessarily. Writing the RAII class yourself gives you much better control over lifetime management for the resource. Smart pointers are just one of many possible examples of RAII. The idiom covers far more than boost::shared_ptrCourses
N
14

The answer to your question is RAII (Resource Acquisition Is Initialization).

But your example is dangerous:

Solution 1 use a smart pointer:

class A
{
  public:
     A(): m_b(new B) {}
  private:
     boost::shared_ptr<B> m_b;
};

Solution 2: Remember the rule of 4:
If your class contains an "Owned RAW pointer" then you need to override all the compiler generated methods.

class A
{
  public:
     A():              m_b(new B)           {}
     A(A const& copy): m_b(new B(copy.m_b)) {}
     A& operator=(A const& copy)
     {
         A  tmp(copy);
         swap(tmp);
         return *this;
     }
    ~A()
     {
         delete m_b;
     }
     void swap(A& dst) throw ()
     {
         using std::swap;
         swap(m_b, dst.m_b);
     }
  private:
     B* m_b;
};

I use the term "Owned RAW Pointer" above as it is the simplest example. But RAII is applicable to all resources and when your object contains a resource that you need to manage ('Owned RAW Poiner', DB Handle etc).

Nerland answered 4/12, 2009 at 11:32 Comment(28)
Why is his example leaky? He allocated on constructor and deallocates on destructorSpermaceti
Because of the compiler generated copy constructor.Nerland
"owns a raw pointer" should probably be expanded to "or a resource handle of any other kind". The same applies for file handles, sockets, database connections or any other resource whose lifetime has to be managed.Courses
Btw, this really shows the power of "the fastest gun in the west" syndrome. This answer is by far more complete and detailed, but because the other one is at the top, it's getting all the upvotes...Courses
RAII: "Resource Acquisition Is Initialization"Whalebone
@Courses it may also be that the code shown is quite disgusting and something you should not do without a good explanation.Tellurate
@PabloAriel code shown is quite disgusting In what way?Nerland
In that you're using dynamic memory for no reason, you put the most important part of the class at the bottom (the member it contains, which tells what the class is meant to manage and thus its reason to exist), in that you use a raw pointer, in that it makes an extra copy of the value of A const &copy, in that it uses using for "using" the function only once, in that it uses four lines for a one-line destructor... as I mentioned, this class can be written as class A{ B m_b = {}; } and it should unless there is a good explanation to do it some other way.Tellurate
There is a myriad of good examples of RAII, such as lock guards, COM+ smart pointers, smart pointers in general (tho the std:: implementations are questionable at the very least), file mappings and so on. There is no reason, however, to do something like in your example, wrapping new() and delete() for no reason whatsoever. Not a real-world example and dangerous to imitate.Tellurate
@PabloAriel: Thanks for the input. I agree with Not a real-world example. But details needed to be given to adequately answer the original question. I personally disagree with all your other points.Nerland
@PabloAriel Given the original question a copy needs to be a true copy. Thus neither std::unique_ptr or std::shared_ptr would actually work (without additional code). unique_ptr would simply prevent the copy and shared_ptr does not give you a new copy but shared data. Now I can see using B m_b is definitely better than using B* m_b in most cases but the OP has chosen an implementation that uses a pointer for a reason (otherwise the simpler solution would have been used). So saying class A{B m_b={};} is not an answer.Nerland
@PabloAriel Understanding how to wrap new/delete is a good idea for all programers. Though best advice is still with using std::"smart pointer" as suggested with the first example.Nerland
@PabloAriel Based on your comments I have the feeling you have also not discovered the Copy and Swap idiom I suggest you do some research on that.Nerland
@PabloAriel Based on your comments I have the feeling you don't understand how to write a standard swap() method. You should probably learn the idiomatic way to write swap.Nerland
I do know about the copy and swap idiom. I also know how to avoid copying data redundantly and unnecessary memory allocations. I don't think there is ever the need to use "new" in a constructor nowadays, but you're free to provide a real-world example if you think there are times in which it's unavoidable.Tellurate
@PabloAriel What about std::vector<> I bet it has a new in its constructor :-) . Basically any time you are creating a container or a smart pointer these are necessary. Just because the standard library has the most common ones does not mean you will never come across situations where the standard ones are not sufficient (so you should know how to do it when you need to). Though admittedly those will be very rare. Everybody should try and create a container/smart pointer (not just for the learning experience, but also to understand how hard they are to do correctly).Nerland
@PabloAriel Now I will be the first to say that in modern C++ you should not be using new/delete (especially when talking to beginners). But when you are in real life and have the experience these things do come up and you should know how to use them. The statement I don't think there is ever the need to use "new" in a constructor nowadays is just wrong. Sure it is rare but not understanding how to do it when you are experienced is an issue. Don't do it when you inexperienced but real life is a lot more complicated and thus experienced user will need to know this.Nerland
Ok, they may need to know it exists and that people do such things, but also they need to know it's not the way they should do it. Even something like std::vector shouldn't have any allocations in its default constructor, only in resize() operations such as copy constructors, but that's a different kind of constructor. Even then, I use malloc() and placement new in order to allocate memory larger than the one requested for faster push(), and there are no matching deletes for such news.Tellurate
@PabloAriel You need to study more if you are using malloc()!!!! (thats a memory allocation system from a completely different language). Yes std::vector<> needs allocation in the default constructor and a deallocation in the destructor!!!!! Please read more about RAII !!!!! But now you are also changing the discussion to the allocation of the elements in the container, sure they need the use of placement new and also require you to manually call the destructor. BUT that has nothing to do with this discussion. Why are you trying to build a straw man?Nerland
but also they need to know it's not the way they should do it. Thats a very overall broad statement. You need to know how to do it because every now and then you will need to do it. Now most of the time you will not need to do it (and when you do I would expect my developers to justify it). But the real life is not as simple as you seem to think it is things are not as simple and cut and dried as you seem to think. You need to know how to do it because it is something you will use.Nerland
I never saw a situation in which you need to do it and I have plenty of developments as example, from web services to 3d games which are available on github. Also, malloc() is still part of C++ (en.cppreference.com/w/cpp/memory/c/malloc), and you need to use it in order to take advantage of placement new which enable important optimizations, though I don't even use that as I have a wrapper for enforcing aligned malloc in the different platforms. My code contains custom smart pointers and std::vector-like classes but the default constructor only initializes members to zero/null.Tellurate
@PabloAriel You don't need to use it to use placement new. You can use the C++ allocators. malloc is part of C. Read the page you linked: It even uses a potentially different memory allocation area. It just happens that there are bindings from C++ to the C language (like there are bindings from nearly every language to C).Nerland
@PabloAriel Sorry this is a classic argument from incredulity: I never saw a situation in which you need to do it . I have been doing this a long time. RAII is the primary mechanism that C++ developers need to learn. I have written several articles on how to do this correctly: See: lokiastari.com/series the section of vectors and smart pointers where I walk through doing them correctly. You should definitely learn how to do it. This is a skill you will need if you want to continue as a C++ developer.Nerland
I know how to do RAII. I use it when it makes sense, such as for mutex locks and file handles. Nothing that you would ever new() in the default constructor, however. My framework includes its own smart pointer implementations and other raii facilities and if you have any critics to it they will be more than welcome. Also, using C headers leads to faster builds than compiling C++ template files.Tellurate
@PabloAriel RAII Is for anything where there is create/doSomthing/destory pattern. They are basically the standard idiom in C++ and used for everything. If you are not writing destructors then you are probably doing something wrong. C headers are not great because they are inconsistent on if they put things in the std namespace. C header files => always global namespace (sometimes std namespace). C++ header files => always std namespace. If you want to swap speed at compile time for safety that's a false saving as the cost of debugging the resulting issues will be orders of magnitude largerNerland
PS. Why would you use RAII for mutex locks std::lock_guard and files std::fstream but not for memory std::vector, when all of these are already in standard? Is there something magical about memory and not other resources?Nerland
I think none of those classes do new() in their default constructor and they shouldn't if they want to avoid plenty of unnecessary allocations, but you're free to look for the code and share it so everyone can see how I'm wrong. ie. with a real world example.Tellurate
No worries about the debugging. It is solved by using my own implementations of things like std::vector. I never use naked arrays or raw pointers.Tellurate
A
2

This technique is best known as RAII - Resource Allocation Is Initialization. It has its own tag on this site.

Alternative, arguably more intuitive names have been suggested, for example:

Aeromechanic answered 4/12, 2009 at 13:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.