7

I am currently reading this tutorial/explanation of rvalue references:

http://thbecker.net/articles/rvalue_references/section_07.html

In the 2nd to last paragraph, the author mentions that "the argument of the copy constructor of T in the body of factory is an lvalue". The code he is referring to is this:

template<typename T, typename Arg> 
shared_ptr<T> factory(Arg const & arg)
{ 
  return shared_ptr<T>(new T(arg));
}

I realise that new T(arg) constructs a T object on the heap, but isn't the returned value a temporary pointer value which will be lost if not used (leading to a memory leak I guess), and hence an rvalue?

EDIT: Just to clarify, I understand that there will be no memory leak in this example. I just meant that, if the pointer value would have not been used we would have no way of accessing the constructed T object and hence we'd get a memory leak.

elatalhm
  • 516
  • 3
  • 12
  • [`std::shared_ptr`](http://en.cppreference.com/w/cpp/memory/shared_ptr) effectively provides you with a _`lvalue`_. – πάντα ῥεῖ Sep 25 '14 at 17:16
  • A shared_ptr will be cleaned up if its not referenced anymore. I.e. if you return a shared_ptr from a method and is not used in the calling code the shared_ptr will be cleaned up automatically. – cageman Sep 25 '14 at 17:16
  • I think `new` returns an `rvalue` which gets copied into the `std::shared_ptr`'s internal `lvalue`. – Galik Sep 25 '14 at 17:25
  • Pretty much all of you are confusing types with value categories. – Lightness Races in Orbit Sep 25 '14 at 18:08
  • 2
    The article is talking about the `arg` in `new T(arg)`, not the entire expression. `arg` is an lvalue. `new T(arg)` is an rvalue. – Raymond Chen Sep 25 '14 at 19:41
  • 1
    Right. The quote _"the argument of the copy constructor of T"_ is referring to `arg`, which is an lvalue. That has got nothing to do with `new` or `shared_ptr`. – Oktalist Sep 25 '14 at 23:51
  • Oh daaammnn. Wow I must have been snoozing. Thanks a lot for pointing that out guys! – elatalhm Sep 26 '14 at 13:36

4 Answers4

6

Short answer, I'm sure someone will write a longer one, but:

new gives you a pointer rvalue: new int = nullptr; fails to compile with error about requiring an lvalue.

Dereferencing that pointer gives you an lvalue: *(new int) = 5; will compile (this naive statement also leaks memory because pointer is lost, of course).

The copy constructor takes a reference, so if you have pointer to object, you need to dereference it.

If you lose the pointer, then you can't delete it, so it won't get destructed and the heap memory will not get freed (until the program exits and memory is returned to the OS).

If you put the pointer to some other object which can take ownership of it, like a shared_ptr, then you do not lose the pointer. The other object will delete it according to its semantics, at the latest when the other object (or the last one, in a case of shared ownership, like with shared_ptr) itself gets destructed.

hyde
  • 60,639
  • 21
  • 115
  • 176
3

Many contributors here seem to be confused about what value categories are, some going as far as to suggest that shared_ptr "stores an lvalue", which makes no sense at all.

Lvalue and rvalueness doesn't really have anything to do with what something "returns" or with a state of objects in memory; it's about the state of expressions in code. Whether an expression is an lvalue or rvalue (or one of the others) comes from various language rules and constructions.

In short, lvalue expressions are names, and everything else is not an lvalue expression. One notable kind-of exception to this rule is *ptr where ptr is of a pointer type, because *ptr is defined to result in an lvalue for historical reasons (unless operator overloading is involved).

Now, it is not explicitly suggested in the standard that new "returns" (evaluates to) an rvalue, because that statement does not make any sense. new evaluates to a pointer to your new block of memory, and by the rules of the language the expression new T is an rvalue, because that's what rvalue means.

It's hard to explain this better without writing a book, but this is the crux of it.

I realise that new T(arg) constructs a T object on the heap, but isn't the returned value a temporary pointer value which will be lost if not used (leading to a memory leak I guess), and hence an rvalue?

Yes, essentially. The fact that it's a pointer to dynamically-allocated memory is completely irrelevant.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
2

Unfortunately the value category of operators and their operands are underspecified which leaves us in an unfortunate position of having to deduce the value category. Many presume that unless explicitly specified the result is a prvalue.

We can make a pretty educated guess by attempting to use new on the left had side of an assignment and see that it does not work and conclude that it does not yield a lvalue but we are still left in unspecified territory.

We can determine it empirically as this code from Luc Danton answer here does:

template<typename T>
struct value_category {
    // Or can be an integral or enum value
    static constexpr auto value = "prvalue";
};

template<typename T>
struct value_category<T&> {
    static constexpr auto value = "lvalue";
};

template<typename T>
struct value_category<T&&> {
    static constexpr auto value = "xvalue";
};

// Double parens for ensuring we inspect an expression,
// not an entity
#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value

and the result of:

std::cout << VALUE_CATEGORY( new int) << std::endl ;

is:

prvalue

which affirms our conclusion using deduction.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Except this only examines a particular implementation of C++ – Yakk - Adam Nevraumont Sep 26 '14 at 00:03
  • @Yakk I spent a while looking into this when I asked [this question](http://stackoverflow.com/q/21053273/1708801) and it seems that though it is officially underspecified as this [comment by Joseph Mansfield](http://stackoverflow.com/questions/14991219/what-is-the-value-category-of-the-operands-of-c-operators-when-unspecified?lq=1#comment21055383_14991297) and from other sources it seems compiler developers have agreed on these things and so I think it is unlikely it will differ although technically they could. – Shafik Yaghmour Sep 26 '14 at 01:34
  • You will also find from [defect reports like this](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#637) that the committee clearly has a clear view of the value categories of these expressions not officially stated in the standard. My hope is that [Joseph's defect report](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1642) will eventually lead to this being more explicitly codified in the standard like the [use of indeterminate values was in C++14](http://stackoverflow.com/q/21319413/1708801). – Shafik Yaghmour Sep 26 '14 at 01:39
1

The return of a new is a prvalue not an lvalue, because you cannot write:

 new T(arg) =  ....;   // not legal, so no lvalue

The standard defines (§3.10): An lvalue (so called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object. And the return of new designates the address of an object, not the object itself.

If you assign this value to a pointer a and dereference that pointer, or even if you would dereference new return value directly, you make it an lvalue:

*new T(arg) = ....;    //  valid, althoug you'd loose the address ! 
*a = ....;     // normal poitner dereferencing 

This is explained in the standard (§5.3.1/1): The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

Important: Your edit and shared_ptr

Your example does not leak: shared_ptr<T>(new T(arg)) creates a shared pointer that takes care of your newly created object, and will delete it if it's no longer used.

Two possibility:

  • the return shared pointer is used in an expression (for example as a temporary shared pointer or assigned to another shared_ptr object): the object is copied and its use count is updated to reflect the number of live references to it. No leak !

  • the retuned shared pointer is ignored: the returned object is destroyed, its use count is decremented, and as it's not used elsewhere, your object is deleted.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    What does that assignment example prove? Declare `const int i = 5`. You cannot write `i = ...`. Yet `i` is an lvalue. Declare `void foo()`. You cannot write `foo = ...`. Yet `foo` is an lvalue. – AnT stands with Russia Sep 25 '14 at 17:25
  • Yes, I agree that the rule of the thumb is not 100% accurate in the sense that it doesn't take into account for non modifiable objects (const and functions). – Christophe Sep 25 '14 at 18:06
  • 1
    Well, function is not an object at all. The concept of "lvalue" has been detached from the anecdotal "can be used left-hand side of assignment" test a long time ago. A much more precise test, derived from the very definition of lvalue, is the test through built-in unary `&` operator. But even that test is not 100% precise. – AnT stands with Russia Sep 25 '14 at 18:14