What is the most efficient way to give a shared_ptr<void>
to an object that passes it to another private method before being stored in it's final destination?
It's difficult to describe... maybe asking with code will illustrate the question better:
class Example{
public:
void give(std::shared_ptr<void> data) { // [A] which signature is best here?
void give(std::shared_ptr<void> & data) { // [B]
// the caller keeps it's shared_ptr
m_give(data); // [A] which of these is preferable?
m_give(std::move(data)); // [B]
}
private:
void m_give(std::shared_ptr<void> data) { // [A] which signature is best here
void m_give(std::shared_ptr<void> & data) { // [B]
void m_give(std::shared_ptr<void> && data) { // [C]
// only `give()` will ever call this
m_data = data; // [A] which of these is preferable?
m_data = std::move(data); // [B]
}
// members
std::shared_ptr<void> m_data;
};
How would you set up a mechanism like this?
CodePudding user response:
An objective standard is needed for defining a "most efficient way".
You clarified that you consider just one reference counting increment. I would suggest a slightly non-intuitive solution which does exactly this:
#include <memory>
class obj {};
class Example {
public:
void give(std::shared_ptr<obj> data)
{
m_give(data);
}
private:
void m_give(std::shared_ptr<obj> & data)
{
m_data=std::move(data);
}
std::shared_ptr<obj> m_data;
};
int main()
{
auto ptr=std::make_shared<obj>();
Example e;
e.give(ptr);
return 0;
}
If you use your debugger to run this step by step you should see that e.give(ptr);
increments the reference count just once, to copy-construct give()
's parameter. The shared_ptr
gets moved into its final resting place, without any further reference counting. The End.
This is not the only way to achieve that, but this approach has the benefit of giving you the option of optimizing this further. If the caller wishes to give up the ownership of the shared_ptr
without any reference counting taking place:
e.give(std::move(ptr));
The caller effectively gives up its reference. Now, stepping through this odyssey in your debugger will show that two moves take place, one to move-construct give()
's parameter, and the same 2nd move. No change to the reference count.
With some further work it might be possible to have your cake an eat it too: either have a single reference count increment to preserve the caller's ownership, and a single move operation when not. But the resulting usage syntax might be a little bit inconvenient. If the main goal is to optimize for the case where the caller preserve its ownership, as you implied, this approach results in, pretty much, a no-brainer but you will still reserve the option to decide on a case by case whether each caller can give up its ownership, and if so squeeze a little bit more juice out of this rock.
CodePudding user response:
Unless the function you are passing the smart pointer to needs to make an internal decision whether to share the data or transfer the data, then I would pass by copy.
class Example{
public:
void give(std::shared_ptr<void> data) {
m_data = std::move(data);
}
private:
std::shared_ptr<void> m_data;
};
This way, you always share ownership because the parameter accepts a copy and you efficiently move the pointer inside the function to avoid a second copy overhead (increasing/decreasing the counter).
If you decide you want to transfer ownership, rather than sharing it you can perform the move when calling the function:
example.give(std::move(data)); // transfer ownership
The problem with passing by reference is that ownership gets transferred (not shared) if you then std::move
the pointer. That means you have less control from outside the function what happens to the pointer you pass to the function.
If the function needs to make the decision between sharing ownership and transferring ownership, then you need to pass by reference to give the function control over the passed in parameter. BUT I would avoid that. It is not ideal design if, after calling a function you then have to check your pointer before using it again because you don't know if the function modified it or not.