0

I am experimenting with smart pointers and try to exchange a few shared_ptr's with unique_ptr's in my project to see if I can gain little performance improvements.

I have the following code snippt:

// --- probably can be replaced with with std::copy ...
if (j != sentences.size()) {
    // also gives me an error if I initialize vector with a size.
    std::vector<unique_ptr<StemmedSentence> > newSentences(j);
    int base = 0;
    for (size_t i = 0; i < j; i++) {
        newSentences[base++] = std::move(sentences[i]); // std::move ??
    }
    sentences = newSentences; // std::move, i guess not !?
}

I copy a vector of pointers into a new vector (in this case a vector of unique_ptr's). Using shared_ptr, this is no problem at all. As I understand, shared pointer uses the implicit assignment, all is working fine. With unique_ptr though, I need to overload the assignment operator (to transfer ownership) and this gives me quiet a headache.

I created this assignment:

ArrayStemmedSnippet& operator=(const ArrayStemmedSnippet& other) {
    if (this != &other) {
        sentences.clear();
        std::vector<unique_ptr<StemmedSentence> >::const_iterator it;
        for (it = other.sentences.begin(); it != other.sentences.end(); ++it) {
            sentences.push_back(std::unique_ptr< StemmedSentence >(*it ? 
                *it : std::unique_ptr< StemmedSentence >())); // --- compiler error
        }
        return (*this);
    }
}

The pointers to copy can contain "Null" pointers. I try to check this in the for loop and copy either the pointer or an empty unique_ptr instance (a nullptr). That line marked with compiler error gives me a problem, I can not figure out the correct syntax. Also I think I have to create a new pointer (clone) if I copy a non-null object. Can somebody please give me a hint on that?

I found a similar post Handling smart pointers in stl container, the answer from Howard Hinnant gave me the inspiration to give it a try. But I can not use that syntax, I have gcc version 4.4.3 on my testmachine.

I have no clue if I will gain more performace, like I said, I experiment. As far as I know I would have no ownership problems in my code, so I thought making a test with unique_ptr and skip the shared_ptr (with all its overhead) might be a little gain in performance. I have about 5 objects where I use shared_ptr, so I would give it a try.

If all this makes no sense, let me know if it is worth playing arround with uniqe_ptr at all.

Thanks in advance!

Community
  • 1
  • 1
Andreas W. Wylach
  • 723
  • 2
  • 10
  • 31
  • 4
    So, you have a vector of unique_ptrs and you want assignment to make copies of all the elements? – R. Martinho Fernandes Aug 03 '12 at 02:39
  • Also, is `StemmedSentence` polymorphic? – R. Martinho Fernandes Aug 03 '12 at 02:45
  • Yes, I have to make copies of all the elements and the StemmedSentence is polymorphic! – Andreas W. Wylach Aug 03 '12 at 02:55
  • 1
    You can't without a clone method. – Puppy Aug 03 '12 at 02:56
  • @DeadMG: I thought about that, but was not sure. Means like I have to implement a clone method in StemmedSentence? Seems this is getting more complicated than I thought :-) – Andreas W. Wylach Aug 03 '12 at 03:01
  • @AndreasW.Wylach I thought this was about performance. I'm not sure how making more copies will help. – R. Martinho Fernandes Aug 03 '12 at 03:08
  • @R. Martinho Fernandes: I am not very experienced with smart pointers, unique_ptr is new for me. The questions is if I assign unique_ptr objects to a new array (just like my loop in the post) I have to make copies or not? Or can I just move the objects to the new element of the array? If a copy is needless, I would ofcourse prefer that! – Andreas W. Wylach Aug 03 '12 at 03:11
  • You can't copy a `unique_ptr` anyway, so the point is moot. – Etienne de Martel Aug 03 '12 at 03:12
  • @Etienne de Martel: But can't I move it or re-assign it? – Andreas W. Wylach Aug 03 '12 at 03:17
  • @AndreasW.Wylach: Do you *need* to copy the vector? Or is the source going to get destroyed and can be *moved*. If you *need* to copy, then you cannot *move*, they are not equivalent operations by any means, in the same way that `shared_ptr` and `unique_ptr` are not equivalent smart pointers, the difference is not their performance, but their *purpose*. – David Rodríguez - dribeas Aug 03 '12 at 03:34
  • @David Rodríguez: In the above snippet I actually need to move the pointers from the old vector to the new vector. The old vector will destroyed. The purpose of the movement to the new vector is simply, that the old vector can contain null objects (due to some trimming) and I want to compact it without these null-objects into a new vector. – Andreas W. Wylach Aug 03 '12 at 03:45
  • @AndreasW.Wylach: That is the kind of information missing from the question. If you just want to compact the vector, you don't need to create a secondary vector or code at all, as this can be done with iterators and standard algorithms... But that **should not** be `operator=`, assignment means getting *two equivalent* objects. – David Rodríguez - dribeas Aug 03 '12 at 03:49
  • @David Rodríguez: So not using std::copy, but iterate over the old vector an move the elements with std::move into the new vector? – Andreas W. Wylach Aug 03 '12 at 03:57

1 Answers1

1

The question makes little sense. The decision of using shared_ptr or unique_ptr should be taken based on whether ownership is shared or unique. The question is focusing on the details of forcing a change in the design that is probably not wanted.

In the original design, multiple vectors holding shared_ptr and sharing the ownership of some other objects, but in the modified approach, because unique_ptr has unique ownership you will be forced to copy the objects. The side effects of the change are multiple: now you need to copy the objects (higher cost). Because the type is a base, you need to implement a clone() approach so that the objects are not sliced, that will also have a (minimal) cost. After the copies are made the vectors will no longer refer to shared objects, so that a changed performed through one of them will not be visible through the rest, changing the semantics.

At this point, I can only start thinking whether you have profiled your code, and how much of the cost of the application is in the copying of the shared pointers, because that is not an expensive operation. My feeling is that you might be barking at the wrong tree, spending time unnecessarily and breaking your application in the process, with a much more obscure approach for nothing.

Sorry to sound negative, but I feel that you should really think this through. unique_ptr is not a replacement to shared_ptr, unless shared_ptr was the wrong solution from the start (objects should not really be shared), but that does not seem the case, since in your current program you are copying the pointers to different containers, and thus sharing.


If what you want is to compact the vector, you don't need copies at all, or even coding:

// [untested, but you can build on top of this]
typedef std::vector< std::unique_ptr< StemmedSentence > > vector_t;

vector_t v = ... // build the vector
// compact:
v.erase( std::remove( std::make_move_iterator( v.begin() ),
                      std::make_move_iterator( v.end() ),
                      std::unique_ptr<StemmedSentence>() 
                    ).base(),
         v.end() );

But this should not be implemented as operator= on the enclosing type taking a constant reference. unique_ptr cannot share ownership, so if the pointers are moved to the new container, then the old container is going to be emptied (this could fit a moving operator=, but not one that promises not to modify the original container.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Basically I use the smart pointer (in this case shared_ptr) because I have a few places where it is not easy to destroy/clean up the objects after usage. It might be a lack in design (I am considering a to re-factor a few things), but for now it does it's job well. I just thought the use of unique_ptr would be a little faster then shared_ptr, that's why I started to play with it. But basically what I read out of your answer and a few comments it makes no sense to change it to a unique_ptr the way I need it. I though I could at leat move the obects. – Andreas W. Wylach Aug 03 '12 at 03:54
  • @AndreasW.Wylach: From a performance point of view, if you allocate your `shared_ptr` with `make_shared`, the extra cost should be minimal. It is not clear from the question whether `shared_ptr` or `unique_ptr` is the solution to your problem, but trying to translate one for the other on the grounds of *performance* makes no sense. – David Rodríguez - dribeas Aug 03 '12 at 03:58
  • The only reason I was thinking about to gain some performance because unique_ptr has less overhead. Actually I am not sure if a change to unique_ptr really works throughout the application, it is just a try. Anyway, if that gain of performance is so minimal, it is not worth the try changing all to unique_ptr and rather leave it as is with shared_ptr. Once again, I am just a little experimenting:-) So I appreciate the thoughts from you more experienced C++ people! Even though, I have the gcc 4.4.3 and the c++0x implementation is surely different from the newer versions. – Andreas W. Wylach Aug 03 '12 at 04:06
  • Thanks for your help and hints, your code example brought up an idea and I combined the deletion of the null-pointers with a loop that marks the sentences for deletion. So overall I spared one loop and all works fine so far. Due to that fact that the old solution was memory leaking (one reason why I used smart pointer to safely cleanup allocated memory). I removed the bug with the new solution and also dropped the smart pointer for this part, so now I have the good old raw pointer with deletion in the destructor. – Andreas W. Wylach Aug 03 '12 at 12:01
  • @AndreasW.Wylach: Removing smart pointers is usually a recipe for bugs, unless you have a really good reason not to use them, I would leave them there, even if it is just for exception safety. It is *really* hard not to leak memory if you are managing data with raw pointers (it is easy not to leak in the main code path, but very hard to guarantee that you won't leak in the event of an exception being thrown anywhere in the code). – David Rodríguez - dribeas Aug 03 '12 at 12:32
  • Exception safety is a good point. True that is. I forgot that in an case of exception the destructor of the smart pointer is called, right? Good point, thanks! – Andreas W. Wylach Aug 03 '12 at 14:31
  • @AndreasW.Wylach: Destructors for all objects in the stack will be called during stack unwinding. That will not call `delete` on raw pointers inside a container (or outside of it for what matters), but it will execute the destructors of smart pointers, which will in turn release the memory. – David Rodríguez - dribeas Aug 03 '12 at 14:38
  • @DavidRodríguez-dribeas: The use of `move_iterator`s to call `std::remove` is unnecessary. It doesn't hurt anything but readability though. The only requirements on `std::remove` is that the `value_type` is `MoveAssignable`. I.e. `std::remove` will be using move assignment internally, even if the iterators return lvalues. – Howard Hinnant Aug 03 '12 at 14:45
  • Yes, that I know. I just wasn't thinking that far. My C++ knowledge is still rusty and I try to get into it again and deepen it. These are all the little important thinks to keep in mind designing a piece of software. – Andreas W. Wylach Aug 03 '12 at 14:47