c++ temporary - "pure virtual method called"
Asked Answered
H

6

6

As I understand temporaries, the following code should work, but it doesn't.

struct base
{
    virtual~base() {}
    virtual void virt()const=0;
};
struct derived:public base
{
    virtual void virt()const {}
};

const base& foo() {return derived();}

int main()
{
    foo().virt();
    return 0;
}

The call to virt() gives a "pure virtual function called" error. Why is that, and what should I do?

Herbage answered 23/1, 2013 at 22:50 Comment(0)
H
5

It seems like you're expecting the const reference to extend the lifetime of the temporary. There are certain situations where this doesn't occur. One of those situations is when returning a temporary:

The second context [in which temporaries are destroyed at a different point than the end of the full-expression] is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

[...]

  • The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

Since calling a member function of the object returned by foo() will necessitate an lvalue-to-rvalue conversion and the object is invalid (not derived from type base), you get undefined behaviour:

If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

Haemin answered 23/1, 2013 at 22:56 Comment(0)
S
8

You're returning a reference to a temporary which is destructed when the function ends at the end of the return statement and you get undefined behaviour.

You cannot return any kind of reference to a temporary and if you return a base by value you'll get slicing, so if you really want this to work, you should return a std::unique_ptr<base>.

Sunnisunnite answered 23/1, 2013 at 22:53 Comment(5)
exactly, because the virtual table would be cleaned up as well... hence the message.Poland
@DougT.: There's no "because" in "undefined behaviour". (Apart from that, virtual tables never get "cleaned up".)Guardian
But shouldn't the temporary object be destroyed at the end of the full-expression, that is after virt() has returned?Herbage
@Herbage no, why would it? The temporary dies at the end of the return statement.Sunnisunnite
@KerrekSB I disagree with your logic. "Undefined behavior" isn't a promise of "undiscernable behavior". If a C++ compiler chose to define certain undefined behaviors, would it stop being a C++ compiler? (Maybe you have a sound answer to that question.) That said, DougT's speculation that a vtable pointer experienced object destruction seems reasonable (even if his terminology is off). He shouldn't rely on patterns noticed in UB, but it seems like you're saying that he's also forbidden from noticing them.Cudbear
H
5

It seems like you're expecting the const reference to extend the lifetime of the temporary. There are certain situations where this doesn't occur. One of those situations is when returning a temporary:

The second context [in which temporaries are destroyed at a different point than the end of the full-expression] is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

[...]

  • The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

Since calling a member function of the object returned by foo() will necessitate an lvalue-to-rvalue conversion and the object is invalid (not derived from type base), you get undefined behaviour:

If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

Haemin answered 23/1, 2013 at 22:56 Comment(0)
W
3

I don't think you can return references to objects like that. The derived() instance goes out of scope as soon as foo() returns. Have

base *foo() { return new derived(); }

and

foo()->virt();

should work.

Whitesell answered 23/1, 2013 at 22:54 Comment(3)
sure, if you want to use shared_ptr<> or the likes, that's what you should do in a real code. However my point is fundamentally that we need to allocate the object on the heap for this to work, why hide that fact under the cloak of pointer-fear?Whitesell
@Whitesell : Because it's just asking for memory leaks. Smart pointers are in the standard library – use them! :-]Bullock
@Whitesell Because we are educating our peers about techniques and good habits to solve problems. unique_ptr doesn't even have runtime penalty in a lot of cases.Ultimatum
A
0

There is no object during the call, the local object was deleted. There will be no vtable to look at. Try:

base& foo() { return *new derived(); }
Antimissile answered 23/1, 2013 at 22:56 Comment(1)
How does the caller know he has to free memory?Ultimatum
O
0

Your temporary 'derived()' object is allocated on the stack. It might have something to do with the object up-casting.

const base& foo() { derived *d = new derived(); return *d; }

Works just fine, for me.

Opossum answered 23/1, 2013 at 22:57 Comment(1)
Sure- thanks for catch. I could have used a boost::shared_ptr<>Opossum
T
0

Change the problem piece to this:

class A {
public:
  const base &foo() { return d; }
private:
  derived d;
};

This way the lifetime of the derived object is as long as A's lifetime, and there will never be any problems.

Toilworn answered 23/1, 2013 at 23:12 Comment(1)
What if I constructed A on the stack of a function bar and returned the reference to d from bar?Ultimatum

© 2022 - 2024 — McMap. All rights reserved.