user12002570
user12002570

Reputation: 1

Using std::move on std::shared_ptr<SomeClass> correctly

I am working on a project and dealing with shared_ptr's. The program is working and giving the correct output. However upon looking at the logs i notice that at some points in the program the reference count is having a particular value. The sample reproducible program is shown below:

SAMPLE Program

#include <iostream>
#include<vector>
#include<string>
#include<utility>
#include<memory>
class NAME 
{   
    public:
    std::string name;
    int age = 12;
    
    
    NAME(std::string m_name, int m_age):name(m_name), age(m_age)
    {
        std::cout<<"NAME Parametrised "<<std::endl;
        
    }
    NAME(const NAME& rhs):name(rhs.name), age(rhs.age)
    {
        std::cout<<"NAME Copy constructor"<<std::endl;
    }
};
class SURNAME 
{
    public:
    std::vector<std::vector<std::shared_ptr<NAME>>> vec_2d;
    SURNAME(const std::vector<std::vector<std::shared_ptr<NAME>>> &m_vec):vec_2d(m_vec) 
    {
        std::cout<<"Refernce count in SURNAME para constructor: "<<vec_2d.at(0).at(0).use_count()<<std::endl;
    }
    
};
class AN
{
    public:
      std::vector<SURNAME> vec_SURNAME;
};

int main()
{

   
   std::vector<std::shared_ptr<NAME>> temp_vector;
   temp_vector.reserve(3);
   temp_vector.push_back(std::make_shared<NAME>("MIDDLE",43));
   temp_vector.push_back(std::make_shared<NAME>("LAST",54));
   temp_vector.push_back(std::make_shared<NAME>("ANOTHERLAST",54));
   std::vector<std::vector<std::shared_ptr<NAME>>> temp_2dvec;
   temp_2dvec.reserve(1);
   temp_2dvec.push_back(temp_vector);//reference count becomes 2
   
   AN obj;
   
   obj.vec_SURNAME.push_back(temp_2dvec);//reference count becomes 3

   return 0;
}

OUTPUT of the above program

NAME Parametrised 
NAME Parametrised 
NAME Parametrised 
Reference count in SURNAME para constructor: 3

As we can see in the output the reference count is 3 inside the constructor. I think that when we write temp_2dvec.push_back(temp_vector); the reference count is increased by 1 and then when we write obj.vec_SURNAME.push_back(temp_2dvec); the reference count again increases by 1 and finally becomes 3. Now my questions are:

  1. Is there a way to avoid this increase in reference count and instead move the share_ptr's ? If yes then how? For example, i tried using temp_2dvec.push_back(std::move(temp_vector)); and then there is no increase in reference count in this step. Is doing this okay here in this step or is it some kind of UB?
  2. When i try to do the same and use the statement obj.vec_SURNAME.push_back(std::move(temp_2dvec)); then the reference count still increases even when i used std::move() here. How is this happening?
  3. Note that the variables temp_vector and temp_2dvec are just temporary variables that i made to hold the vectors. They are used only once for pushing purposes. So i will not use them. So it is safe to move from them. How can i safely achieve this, that is no increase in reference count should be there when pushing onto the vectors ?
  4. Also, are my 2 comments correct(those which i wrote just to the right of statements in the main program)? That is, the reference count are increased at those 2 steps or are they increased at some other area/step of the program which i am missing.

Upvotes: 0

Views: 799

Answers (1)

dewaffled
dewaffled

Reputation: 2963

I tried using temp_2dvec.push_back(std::move(temp_vector)); and then there is no increase in reference count in this step. Is doing this okay here in this step or is it some kind of UB?

std::move does not move or optimize anything by itself, it is just a type cast. But the code consuming moved object can handle it differentely because of different argument type. Standard library classes do it, e.g. for std::vector:

Move assignment operator. Replaces the contents with those of other using move semantics (i.e. the data in other is moved from other into this container). other is in a valid but unspecified state afterwards.

So, yes, you should use std::move and at least the standard library will handle it more effectively.

When i try to do the same and use the statement obj.vec_SURNAME.push_back(std::move(temp_2dvec)); then the reference count still increases even when i used std::move() here. How is this happening?

But your current code does not:

    std::vector<std::vector<std::shared_ptr<NAME>>> vec_2d;
    SURNAME(const std::vector<std::vector<std::shared_ptr<NAME>>> & m_vec):vec_2d(m_vec) 
    {
        std::cout<<"Refernce count in SURNAME para constructor: "<<vec_2d.at(0).at(0).use_count()<<std::endl;
    }

You have m_vec argument and copy it in vec_2d. here is where reference count = 2 comes from. you can handle moved objects more effectively by defining an additional separate constructor:

    SURNAME(std::vector<std::vector<std::shared_ptr<NAME>>> && m_vec):vec_2d(std::move(m_vec))
    {
        std::cout<<"Refernce count in SURNAME para constructor: "<<vec_2d.at(0).at(0).use_count()<<std::endl;
    }

here the argument type is different std::vector<std::vector<std::shared_ptr<NAME>>> && and it can be safely moved it instead of copying with vec_2d(std::move(m_vec)).

Another approach is to have a single method that always get argument by value instead of having multiple overloads for copy and move:

    SURNAME(std::vector<std::vector<std::shared_ptr<NAME>>> m_vec):vec_2d(std::move(m_vec))
    {
        std::cout<<"Refernce count in SURNAME para constructor: "<<vec_2d.at(0).at(0).use_count()<<std::endl;
    }

here the argument is always moved, but the argument itself is either copy-constructed or move-constructed depending of the caller. this way you need to write only one method instead of two but this only helps when you always want to copy/move inside. if the copy needs to be conditional it is better to have separate overloads for copy and move.

Upvotes: 1

Related Questions