I have some confusion with this smart pointer thing, although reading the theory I can more or less identify the differences between unique_ptr
, shared_ptr
and weak_ptr
I still can't master them.
I have a project that I am doing in layers, in the lowest layer which queries the database engine, I am using a std::vector
to save the values returned by the query and return them through the different layers to the user, the vector or more well "matrix" is as follows:
std::vector<std::vector<std::string>> nombreVector;
I use vectors because I know how many rows and columns the query returns, so I can initialize the size of the vector in a fixed way without having to redefine it.
The querying function has this form:
typedef std::vector<std::vector<std::string>> RetornoConsulta;
const RetornoConsulta* consultar_tabla_x(int codigoTabla)
{
RetornoConsulta *retorno = nullptr;
try
{
retorno = extraer_datos_consulta(codigoTabla);
}
catch(std::exception &ex)
{
throw ex;
}
return retorno;
}
I don't want to go into too much detail, because this function is divided by tons of other functions, but this is a general idea, I was using a raw pointer, because I have every intention that the memory of the array "exists" even outside the scope of the function and I do not want to send the vector by value, because this function is encapsulated in another function of the upper layer and that in turn is encapsulated in another, imagine sending a vector of 1,000 or more records by value, being copied unnecessarily once and again.
Reading about exceptions I found that this way of using raw pointers can cause a memory leak when an unexpected exception is thrown and smart pointers are strongly recommended.
This is where my confusion comes in, I want to use that vector with a smart pointer, something like, unique_ptr<RetornoConsulta> ptrConsulta
and have my function return that.
I know that sending a unique_ptr
by reference is not funny (or so I've read), but I want my function to return the reference of the vector which is encapsulated by unique_ptr
.
Is it enough to just return unique_ptr
by value? since the value of this is the reference of the vector... something like:
typedef std::vector<std::vector<std::string>> RetornoConsulta;
unique_ptr<RetornoConsulta> consultar_tabla_x(int codigoTabla)
{
unique_ptr<RetornoConsulta> ptrRetorno;
try
{
//unique_ptr fue inicializado dentro con make_unique<RetornoConsulta>(nFilas)
//Y regresado por valor de la misma forma que esta función la retorna
ptrRetorno = extraer_datos_consulta(codigoTabla));
}
catch(std::exception &ex)
{
throw ex;
}
return ptrRetorno;
}
What shorter answer is this going to be: yes , you are using it well :-)
The main difference between a
std::shared_ptr< >
and astd::unique_ptr< >
is very simple: the former can be copied ; the second, cannot be copied .Hence the difference in the name:
std::shared_ptr< >
: shared pointer . Several instances can point to the same physical object , copying each other. eldeleter
is not called until the last instance ofstd::shared_ptr< >
el is destroyed .std::unique_ptr< >
: single pointer . Only one instance can point to a physical object . Hedeleter
is called as soon as the only instance ofstd::unique_ptr< >
him that can exist is destroyed.Both types can be returned by copy perfectly; in fact, it is the correct way to do it:
If the compiler is bobo , in both cases it will call the move-copy-constructor to return the value; and, if you're a little smarter (and circumstances allow) you'll do a , calling the constructor
copy elision
directly , saving you the move-copy-constructor calls and the move operations involved.Why don't we check it out? Let's use a sneak object that tells us about your life:
The object
S
will show us a message when it is built (S()
) copied (S(const S &)
) or moved (S(S &&)
). Let's see how it behaves when we return it as a value from a function:The above code shows the output:
We see a single build in both cases, but what if we disable 2 the compiler's optimization of return value 1 ? The output changes like this:
We see that in the first case, an instance has been built
S
once, and in the second case it has been built and moved. Now let's see the same thing with smart pointers with optimizations:The output is:
That is: a single construction of the object. Disabling optimization of return value 1 the output is:
The same! Therefore, to your question of Is it enough to just return
unique_ptr
by value? the answer is: Yes . You can see this code sample on Wandbox .-fno-elide-constructors
in gcc.To solve this problem it is not necessary to resort to smart pointers:
(Example that part of the code exposed by @PaperBirdMaster )
What the template
miFunc
does is call itself recursively a given number of times (I don't like repeating code).The program itself recursively calls 10 times
miFunc
and notice that each call returnss
. According to your fears the program should create 11 objects of typeS
... but it won't. This is the output of the program:That is, only one instance of S is created... not 11. This is where the copy omission is coming in , as @PaperBirdMaster has rightly commented, which as you can see does not only apply to STL containers.
However, let's see how easy it is to break this system:
With this change the execution result changes considerably:
Why does this happen? Well, what we have done with this last change is to force
miFunc
an instance of to be created in each call toS
, then the state of the instance returned by is copied to this instancereturn
and, of course, since we now have two objects, one of the two must be eliminated... Although this version of the program is somewhat less optimal than the first, its execution will continue to be much faster than you think.Note that the traditional assignment operator is not being called, but the move operator
S& S::operator=(S&&)
.If the returned object supports movement operations and makes use of dynamic memory, this operation is practically instantaneous since the final object appropriates the pointers of the source object... and copying pointers is practically free. In your case, you are lucky that it
std::vector
makes use of this feature, so you should not worry about this type of copying.Ok, we see that we
return
shouldn't worry about it... And what happens then when we pass this object to a bunch of functions?It is clear that if we pass it a copy, the performance is going to suffer. How about we use references?
As we see in the program output, the number of copied objects is 0:
So my conclusion is that your arguments do not justify the use of smart pointers. There is no need to complicate the code unnecessarily.