13

Let's say that I have some arbitrary class, A:

class A {
 //... stuff
};

I want to call into an external API that takes in a shared pointer to some type, like so (I cannot change this interface):

//...much later
void foo(std::shared_ptr<A> _a){
    //operate on _a as a shared_ptr
}

However, in the (legacy) code I'm working with, the class A instance I'm working with is allocated on the stack (which I cannot get around):

A a;
//...some stuff on a
//Now time to call foo

On top of this, an instance of class A is quite large, on the order of 1 GB per instance.

I know I could call

foo(std::make_shared<A> a);

but that would allocate memory for a copy of A, which I would really like to avoid.

Question

Is there a way to hack together some call to std::make_shared (possibly with move semantics) so that I am not forced to allocate memory for another instance of class A?

I've tried something like this:

foo(std::make_shared<A>(std::move(a)));

But from what I can tell, a new instance of A is still created.

Example code

#include <iostream>
#include <memory>
using namespace std;


class A{
    public:
    A(int _var=42) : var(_var){cout << "Default" << endl;}
    A(const A& _rhs) : var(_rhs.var){cout << "Copy" << endl;}
    A(A&& _rhs) : var(std::move(_rhs.var)){cout << "Move" << endl;}
    int var;
};

void foo(std::shared_ptr<A> _a){
    _a->var = 43;
    cout << _a->var << endl;
}

int main() {
    A a;
    cout << a.var << endl;
    foo(std::make_shared<A>(std::move(a)));
    cout << a.var << endl;
    a.var = 44;
    foo(std::make_shared<A>(std::move(a)));
    cout << a.var << endl;
    return 0;
}

Output:

Default
42
Move
43
42
Move
43
44

AndyG
  • 39,700
  • 8
  • 109
  • 143

3 Answers3

19

This is possible with the shared_ptr constructor that allows for an "empty instance with non-null stored pointer":

A x;
std::shared_ptr<A> i_dont_own(std::shared_ptr<A>(), &x);

(It's "overload (8)" on the cppreference documentation.)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • The `template shared_ptr(const shared_ptr& r, T *p) noexcept` constructor signature is available in C++11: it's detailed in [util.smartptr.shared.const]/13 through /16. – Casey Jul 14 '14 at 16:33
  • 3
    @KerrekSB: I've never seen this constructor before, it seems incredibly strange and dangerous – Mooing Duck Jul 14 '14 at 16:34
  • 2
    @MooingDuck: It *is* incredibly strange and dangerous. The OP is doing something incredibly strange and dangerous, and is probably feeling incredibly strange, too. – Kerrek SB Jul 14 '14 at 16:36
  • 7
    @MooingDuck It's meant to enable creation of e.g. a `shared_ptr` to a member of an object while sharing ownership of the object itself. Using it this way definitely qualifies as a hack ;) – Casey Jul 14 '14 at 16:37
  • 1
    @Casey: Ooooh, that makes more sense! – Mooing Duck Jul 14 '14 at 16:39
  • @KerrekSB Some days I feel so strange I don't know if I even want to wear socks. – AndyG Jul 14 '14 at 16:39
  • @Casey: Right - the responsibility for the correctness of this use depends lies with the caller: The object pointed to by the raw pointer must live at least as long as the created shared pointer. This can be trivially achieved if the raw pointee is a subobject of the shared pointee, or by other means (as in the OP's case). – Kerrek SB Jul 14 '14 at 16:45
  • 4
    +1 This is butt-ugly code I could learn to love. Unlike the null-deleter solution, this construction allocates no memory and is officially `noexcept`. One quirk: the client `foo`, if it bothers to check, is going to see `a.use_count() == 0`, even though `a.get()` returns the correct non-null pointer. – Howard Hinnant Jul 14 '14 at 23:37
7

If you know that shared pointer you pass to foo() will not get stored, copied etc, ie will not outlive your object you can make std::shared_ptr pointed to object on the stack with empty deleter:

void emptyDeleter( A * ) {}

A a;
foo( std::shared_ptr<A>( &a, emptyDeleter ) );

Again you need to make sure that shared pointer or it's copy will not outlive the object and well document this hack.

Slava
  • 43,454
  • 1
  • 47
  • 90
  • 2
    It's hard to imagine a case where he can't change the interface but can make sure the pointer won't outlive the object. – David Schwartz Jul 14 '14 at 16:29
  • Wouldn't that make it of a different type? OP cannot change the function signature, so this wouldn't work. – juanchopanza Jul 14 '14 at 16:30
  • If the function takes a `shared_ptr`, that's usually because it stores a copy of the smart pointer somewhere and then returns, this solution is likely to cause undefined behavior. – Mooing Duck Jul 14 '14 at 16:32
  • 1
    @juanchopanza: No, `shared_ptr` type-erases the deleter. That's only an issue with `unique_ptr`. – Mooing Duck Jul 14 '14 at 16:32
  • 1
    @juanchopanza no it would not – Slava Jul 14 '14 at 16:33
  • 2
    @DavidSchwartz it is not hard to imagine that instance of class A is created in `main()` and will outlive API, that requires shared_ptr. Anyway I think limitation of the solution is clear from the answer. – Slava Jul 14 '14 at 16:39
5

Assuming class A supports move semantics, do this:

std::shared_ptr<A> newA = make_shared<A> (std::move (_a));

Do not use _a anymore, use only newA. You can now pass newA to the function.

If class A does not support move semantics, there is no safe/sane way to do this. Any hack will only happen to work, and may break in the future. If you control enough of the class code, you may be able to add support for move semantics.

But from what I can tell, a new instance of A is still created.

Why do you care? What you're trying to avoid is copying all the data in the instance, and this does that.

The point of move semantics is to move the data from one instance to another without having to do an allocate/copy/free. Of course, this makes the original instance "empty", so don't use that anymore.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278