3

The following slide is from Bjarne Stroustrups talk "The Essence of C++":

enter image description here

Do I understand his techniques with the following simple examples (values, raw pointers, smart pointer for resource members/subobjects)?:
1.

class foo{  
    std::vector<bar> subObj;  
    int n;  
public:  
    foo(int n) : n(n) {
        subObj.push_back(bar("snickers"));
    } 
};

2.

class foo{  
    std::vector<bar>* subObj;  
    int n;  
public:
    foo(int n) : n(n){
        subObj = new std::vector<bar>();
        subObj->push_back(bar("snickers"));
    } 

    ~foo() {
        delete subObj;
    }
};

3.

class foo{    
    std::unique_ptr<std::vector<bar> > subObj;  
    int n;  
public:   
    foo(int n) : n(n){  
        subObj(new std::vector<bar>());  
        subObj->push_back(bar("snickers"));  
    }
}; 

Why is 1. preferable over 2.? If I instantiate a foo object and dereference it in order to get the value of the small n member, the vector member will be loaded into memory as well, right? With 2, only the pointer is loaded into memory! For this question I want to assume that the vector will be somewhat large during execution.

Also, why is 2 (RAII) preferable over smart pointers? Isn't the overhead pretty similar (both destruct the resource after the lifetime)?

Patrick
  • 1,717
  • 7
  • 21
  • 28

3 Answers3

3

I believe bullet 2 is targeted at other resources such as network connections, file handles, connections to audio/video devices, etc.

If an instance of your class opens a network connection, you need to make sure that the destructor of the class closes the connection.

If an instance of your class opens a file or directory, you need to make sure that the destructor of the class closes the file or the directory.

If an instance of your class opens a connection to your audio device, you need to make sure that the destructor of the class closes the connection.

If an instance of your class changes the display of the program to use the entire screen instead of a window, the destructor of the class needs to make sure that display is restored to window mode.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
3

Whatever 2 is, it's not preferable over anything. It is an accident waiting to happen as soon as you make a copy of a foo object.

Why is 1. preferable over 2.? If I instantiate a foo object and dereference it in order to get the value of the small n member, the vector member will be loaded into memory as well, right? With 2, only the pointer is loaded into memory!

This is pretty confused - everything here is in memory already so what exactly are you loading into memory? It is true that the foo in 2 is smaller than the foo in 1, so you can fit more foos in the same cache line, potentially yielding better performance when you operate on an array of foos without touching the vector.

But a vector itself is little more than three pointers (for begin, end, capacity), so it's not as if it's a huge locality loss. The contents of the vector can be arbitrarily large (up to a limit, of course) but they are elsewhere. And the tiny locality gain from the smaller size of foo will in all likelihood be erased by the extra level of indirection whenever you actually need to use the vector.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • You're right. I just looked at an internal std::vector implementation and, unlike an array, the data goes into the heap. It makes sense now that I think about it (for how would one regrow a vector that was allocated in the stack if one doesn't make use of the heap internally?). I'm a programming beginner; could you please explain to me how I can improve code snippet 2? – Wuschelbeutel Kartoffelhuhn Mar 21 '15 at 06:42
  • 1
    @WuschelbeutelKartoffelhuhn http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three – T.C. Mar 21 '15 at 07:26
1

First of all, about the scope of that presentation/slide: As far as I remember, Strostrup was mainly talking about local variables in functions and not so much about member variables. Also, the point he wanted to make was that a garbage collector in c++ ist (almost) unnecessary, because there are other - arguably better - mechanisms (build on top of destructors). So you should not put too much weight on the order of points 1-3. Which technique you use depends more on the use case and what kind of resource we are actually talking about.
Now about the different techniques:

  1. Containers: Obviously, you should only use containers, if you actually want to manage multiple items. This is mainly in contrast to C-Style arrays. So what you shouldn't do is:

    Ressource rc[]=new Ressource[10]; 
    

    and instead use e.g. a vector:

    std::vector<Ressource> rc(10);
    

    which has the advantages that copying the vector also copies the contained elements (value semantics) and as soon as the vector is destroyed (e.g. goes out of scope) the elements are automatically destroyed. Both mechanisms are not provided by C-Style arrays.

  2. RAII: If you have to ensure, that a resource has to be released at some point in time, you should wrap it into a RAII class. This can be memory, (think e.g. of a large matrix) but is especially known in combination with system resources like threads, mutexes, files or sockets. To make that clear: in your example 1, the resource you are "managing" is a collection of bars and the vector is the technique to do it. In example number 2, the resource is std::vector<bar> and foo is the RAII-class. Now, if your RAII class manages memory (as in your case) it should use smart pointers internally.
    A rule of thumb: DON'T EVER USE new and delete - always use either std::make_unique or std::make_shared instead (as always, there might be special cases, where you have to break that rule).

  3. This brings us to smart pointers. Basically they are a replacement for the classic new/ delete combination. While they are good at automatically freeing memory, they have some drawbacks compared to a full-fledged RAII class: 1) They only work for memory, but e.g. cannot acquire a lock or a file (but of course can be used to hold other RAII classes). 2) They don't provide value semantics (copying a share_ptr doesn't copy its contents and copying a unique_ptr isn't possible). But again, they are great for IMPLEMENTING some types of RAII classes and if you just want to create single objects on the heap (free store).

So, to wrap things up: your examples are not showing different techniques (container, RAII, smart pointer) for the same kind of resource, but you are managing different kinds of resources by different mechanisms. From your examples, number 2 is strictly worse than 1 and 3, as it manually calls new and delete (requiring an extra function and adding an additional source for errors). Whether 1 or 3 is better depends a little on your application. In general, I would prefer 1, as it automatically implements value semantics, while 3 would require a manual implementation of copy/move assignment operator / constructor. However, If you absolutely need to minimize the memory of foo itself (not the total memory footprint), then 3 saves the size of 2 pointers compared to 1 (ignoring possible other effects due to padding).

MikeMB
  • 20,029
  • 9
  • 57
  • 102