-1
#include <stdio.h>
#include <memory>

struct A {
  A() { printf("A()\n"); }
  virtual ~A() { printf("~A()\n"); }
};

struct B : public A {
  B() { printf("B()\n"); }
  virtual ~B() { printf("~B()\n"); }
};

A&& foo() {
  B b;
  return std::move(b);
}

int main() {
  A&& a = foo();
//  const A& a = foo(); // same output
  printf("checkpoint\n");
}

output:
A() 
B() 
~B() 
~A() 
checkpoint

checkpoint is the last line, why? I want foo to return reference instead of pointer to achieve polymorphism, is there a way to do it?

PS: this question may be related: Why a & b have the same address?

And what's more, I simply what a scheme that return an object created within the function, but not using pointers. For one reason pointers need delete, For another reason, think A & B are iterators that support range-based for loop, whose de-referencing operator* is redefined, using pointers or unique_ptr/shared_ptr is not a good idea, i guess.

willzeng
  • 915
  • 1
  • 8
  • 19
  • 3
    It's OK to return an lvalue/rvalue reference, but not a dangling one. – Evg May 14 '20 at 07:20
  • 1
    Function-local variables are destroyed when function returns so its rvalue reference becomes invalid. Same happens with pointer and lvalue reference so there are nothing special about rvalue reference in that sense. – Öö Tiib May 14 '20 at 07:23
  • Then how to return a non-dangling one created within with move semantics? No pointers, no unique_ptr or shared_ptr that redefines operator*, since I'm making A & B some kind of iterators that also define their own operator*. – willzeng May 14 '20 at 08:11
  • Just do `B foo() {B b; return b;}`? – HolyBlackCat May 14 '20 at 08:37
  • foo have to return A, caller don't know B. The question is "how to return a base type reference instead of a base type pointer, but with zero overhead (no copy)" – willzeng May 14 '20 at 08:59

3 Answers3

3

This is ill-formed because foo() is returning a reference to local object which would be destroyed when foo() returns; then the returned reference is always dangled.

You might use smart pointers (like std::unique_ptr) instead. e.g.

std::unique_ptr<A> foo() {
  std::unique_ptr<A> b = std::make_unique<B>();
  return b;
}

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
0

See this Answer: Is returning by rvalue reference more efficient?

A&& foo() { With B b; return std::move(b); } You are returning an dangling reference: https://godbolt.org/z/HU88aU

I do not quite understand what returning a reference (and not a pointer) has to do with polymorphism? B is based on A and with creating the instance in foo() you exceed the lifetime of the object - thats all.

Wolfgang
  • 320
  • 3
  • 12
  • Because returning a pointer requires a delete. Even unique_ptr or shared_ptr does not really help, since they redefined operator*. Think A is some kind of abstract iterator, B is a concrete iterator. If refs can be used instead of pointers, it helps. – willzeng May 14 '20 at 08:07
0

This is wrong ony many levels.

Polymorphism doesn't have anything to do with using references vs pointers. Polymorphism occurs for example when calling a member function of an object out of a class hirarchy. It will cause a different function to be executed depending on the exact type of the object. It doesnt matter if you have a pointer to that object and call foo->bar() or a reference to it and call foo.bar(). You get polymorphism either way.

By manually declaring a destructor you prevented the move constructor and move assignment operator to be automatically generated. When trying to move something that doesn't have move semantics it gets copied instead. So even if you just add a destructor for logging you need to manually enable moving:

A(A&&) = default;

You don't seem to understand how rvalue references work. I am not going to explain the whole topic in detail here but std::move essentially just converts an lvalue to an rvalue. You don't need to declare an lvalue and convert it to an rvalue to get an rvalue. So this:

B b;
return std::move(b);

can instead just be written as:

return B {};

That changes your code to this:

#include <iostream>
#include <memory>

struct A {

    A()
    { 
        std::cout << "A()" << std::endl;
    }

    A(A&&) = default;   

    virtual ~A()
    { 
        std::cout << "~A()" << std::endl;
    }
};

struct B : public A 
{
    B()
    {
        std::cout << "B()" << std::endl;
    }

    B(B&&) = default;

    virtual ~B() 
    { 
        std::cout << "~B()" << std::endl;
    }
};

A foo() 
{
    return B {};
}

int main() 
{
    A a = foo();

    std::cout << "checkpoint" << std::endl;
}
Eric
  • 1,183
  • 6
  • 17
  • I understand how reference works. I don't understand rvalue ref deeply since I just read it for two days. I just want use it to solve my problem. Your code doesn't help, there's a copy operation. I want create and return through ref, without any copy. – willzeng May 14 '20 at 08:41
  • @willzeng no you don't understand. There is no copying in my code. In fact A and B even have their copy constructor and assignment operator deleted because i declared a move constructor. A foo; a bar; foo = bar; won't even compile. – Eric May 14 '20 at 08:47
  • just compile & run & see the output. I don't want any destructor before "checkpoint" – willzeng May 14 '20 at 08:50
  • @willzeng That's not how moving works in c++. The moved-from object doesn't just disapear, it has to be in a valid state and it's destructor will run. – Eric May 14 '20 at 08:55
  • You mean the goal cannot be achieved in the first place? You may see another post "Why a & b have the same address" in my question, the a & b even have the same address. That inspired me to seek this approach. – willzeng May 14 '20 at 09:10
  • What you want to achieve is something called copy elision. That is a compiler optimization. It will happen under certain circumstances but you won't be able to always have the move get optimized out. Once the move is happening you will inevitably get the destructor of the moved-from objectcalled. https://stackoverflow.com/questions/38043319/how-does-guaranteed-copy-elision-work – Eric May 14 '20 at 09:29
  • You may see https://stackoverflow.com/questions/61812074/how-to-simplify-the-adapter-scheme, that's the real question I'm thinking. – willzeng May 15 '20 at 05:07