0

I'm having trouble understanding the (to me intricate) mechanisms executed behind the scenes in the following code example:

#include <utility>
#include <memory>
#include <iostream>

struct A {int a;};
struct B {int b;};

std::pair<std::shared_ptr<A>, std::unique_ptr<B>&> FuncA() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::unique_ptr<B> b = std::make_unique<B>();

    a->a = 12; b->b = 13;

    std::cout << "FuncA a: " << a.get() << std::endl;
    std::cout << "FuncA b: " << b.get() << std::endl;

    std::cout << "FuncA a.a: " << a->a << std::endl;
    std::cout << "FuncA b.b: " << b->b << std::endl;

    return {a,b};
}

void FuncC(std::pair<std::shared_ptr<A>, std::unique_ptr<B>&> input) {
  std::cout << "FuncC a: " << input.first.get()  << std::endl;
  std::cout << "FuncC b: " << input.second.get() << std::endl;

  std::cout << "FuncC a.a: " << input.first->a  << std::endl;
  std::cout << "FuncC b.b: " << input.second->b << std::endl;
}

void FuncB() {
  auto ret = FuncA();
  
  std::cout << "FuncB a: " << ret.first.get()  << std::endl;
  std::cout << "FuncB b: " << ret.second.get() << std::endl;

  std::cout << "FuncC a.a: " << ret.first->a  << std::endl;
  std::cout << "FuncC b.b: " << ret.second->b << std::endl;

  FuncC(ret);
}

int main(){
  FuncB();
}

I've compiled the code with both GCC and Clang which give similar results:

FuncA a: 0xfeaec0
FuncA b: 0xfeaed0
FuncA a.a: 12
FuncA b.b: 13
FuncB a: 0xfeaec0
FuncB b: 0x7ffd1c8e4a00
FuncC a.a: 12
FuncC b.b: 479087264
FuncC a: 0xfeaec0
FuncC b: 0x406100
FuncC a.a: 12
FuncC b.b: 1449378512

As is clearly visible, the address of the std::unique_pointer reference (and of course also its value) are not the same as within FuncA, but the address and value of the std::shared_pointer are unchanged.

What's happening here, and what (if anything) could be done to make the reference-passing correct?

Is some form of copy-constructor being executed on the std::unique_ptr as a result of returning from FuncA?

  • 3
    This is exactly the same thing as returning a reference to a local `int`. Sometimes, it just looks like it works until some change to a seemingly unrelated part of the code causes it to stop working for not obvious reason. – François Andrieux Feb 10 '22 at 18:08
  • 2
    Returning a *dangling reference* is never going to go well. – Eljay Feb 10 '22 at 18:08
  • 2
    `FuncA` is not returning a pair with a `std::unique_ptr`. It is returning returning a pair with a *reference* to a `unique_ptr`. A reference to an object on the stack that's going to be cleaned up when the function exits. `unique_ptr` is for ownership, not sharing – Joe Feb 10 '22 at 18:09
  • Remember that, in C++, observing that something works is in no way evidence that it actually works. Because of Undefined Behavior, incorrect code can often look like it works.You can never assert that code is correct because it produces the expected result. – François Andrieux Feb 10 '22 at 18:11
  • Does this answer your quesiton? [C++ Returning reference to local variable](https://stackoverflow.com/questions/4643713/c-returning-reference-to-local-variable). – François Andrieux Feb 10 '22 at 18:12
  • Change the `std::unique_ptr&` to `std::unique_ptr` and try again. A reference is 8 bytes and the `unique_ptr` itself is 8 bytes with one dereference less. Should be as performant, if not more performant. There are some classes, whose objects should usually (only few exceptions) be returned or given as parameter by value. unique_ptr or string_view belong to this group. – Sebastian Feb 10 '22 at 18:24

1 Answers1

1
std::pair<std::shared_ptr<A>, std::unique_ptr<B>&> FuncA() {
    // ...
    std::unique_ptr<B> b = std::make_unique<B>();
    // ...
    return {a,b};
}

A local std::unique_ptr<B> is created and a reference to it is returned as the second element in the pair. This is a dangling reference and is later accessed, giving the program undefined behaviour.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Thank you, I supposed that it was something like this that happened. I'm a bit at a loss as to why returning a reference in this manner isn't caught by the compiler. – Chris Rajula Feb 10 '22 at 18:22