0

Is there anyway in C++11 to have an input argument to a function be declared as invalid after the function returns?

For a simple example, consider that I have a rectangle object with a coordinate for the lower left corner and a coordinate for the upper right corner. If I pass this rectangle object by non-const reference to the function, the function is free to side-effect the rectangle memory in place as it sees fit. What if that function needs to scale it and rather than copying to new memory just wants to work in place, how could we in C++11 declare that after the function returns, the contents of the rectangle passed in are no longer valid as they may have been modified by the function called?

I would not want to modify the rectangle class, but instead have some way in the declaration of the function call to indicate that the memory passed in by reference should be considered invalid upon return from the function and have the compiler give an error if the caller tries to use it subsequent to the function call returning. Is there some way to do this in C++11?

WilliamKF
  • 41,123
  • 68
  • 193
  • 295
  • 3
    You could use `Boost.Optional`, pass *that* by reference, and have the callee destroy the rectangle when it's done. – Kerrek SB Dec 14 '11 at 12:53

4 Answers4

3

You can get half-way there:

void function(rectangle&& x) // r-value reference
{
    // ...
}

rectangle r;

function(r); // error
function(std::move(r)); // okay, explicitly made into an rvalue

// now (by convention) r shouldn't be used, because it's been moved

That said, the type-system is C++ is not strong enough to give you an error if you use it again.

I question your design, though. What's wrong with making a copy? Or why not make it clear in the function name that the argument is going to modified? (Like std::swap.)

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Random note: I've had occasion to `std::move` a member `std::vector` in the return of a method (it was a buffer that fills asynchronously and the user polled to get the data) and then continue using the vector assuming it had been emptied. Maybe I'm wrong, but I find that usage fine. – David Dec 14 '11 at 12:47
  • @Dave: You may have found it fine, but it is undefined behavior. – Björn Pollex Dec 14 '11 at 12:49
  • 2
    @Dave: I just spent a few minutes reading the standard, and as far as I can tell the state of `rv` is unspecified. So you should probably explicitly assign it a new value. – GManNickG Dec 14 '11 at 12:55
  • @BjörnPollex It's undefined for a `std::vector`? It can't be undefined for *any* object, I can write a class and I'll know exactly what my move ctor/assign op do. I should change my vector move heh – David Dec 14 '11 at 12:55
  • 2
    @BjörnPollex: I don't think it would be undefined, just unspecified. A moved-from value is still just a value, we just don't know what you can do to it. – GManNickG Dec 14 '11 at 12:56
  • @Bjorn: It is not undefined. The state is unspecified, but *valid*. I would think that means that all member functions still conform to their specifications. – Puppy Dec 14 '11 at 12:58
  • A copy would work correctly too, the idea is that since caller should be done with the memory, it is faster for callee to work in place instead of copying to a new memory location. So if I am doing many millions of these operations, it could have an observable impact on performance. – WilliamKF Dec 14 '11 at 13:01
  • 2
    "it is faster for callee to work in place instead of copying to a new memory location" Stop. Design for elegance, not speed. Speed falls naturally from elegance, and for the final 1% you can *profile* your application and find out where your design needs to be subverted. Until then, you make a copy and you operate on that copy. (Or do what Boost does and make two versions, `x` and `x_copy`. Note the latter uses the former.) "it *could* have an observable impact" Sure, but *does* it? You have no idea, and you shouldn't be designing around problems you don't know exist. – GManNickG Dec 14 '11 at 13:16
1

Pass a Boost.Optional as a reference to your function and have the function reset the variable at the end.

void MyFunction(boost::optional<Rectangle> &rectangle)
{
    // Do something with rectangle here
    rectangle.reset();
}

boost::optional<Rectangle> rectangle(Rectangle(0, 0, 0, 0));
// You can also set the optional rectangle to another value
rectangle = Rectangle(100, 100, 200, 200);
MyFunction(rectangle);
// Now rectangle won't be initialised and shouldn't be used
ASSERT(!rectangle.is_initialized());

Having said all that, this sounds a bit like a design issue. Can you re-architecture your functions so this isn't necessary?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Mark Ingram
  • 71,849
  • 51
  • 176
  • 230
  • Does boost::optional work on stack memory or just heap memory? My rectangle is on the stack. – WilliamKF Dec 14 '11 at 13:44
  • Your rectangle object won't exist on the stack anymore, it will only be contained within the `boost::optional`. – Mark Ingram Dec 14 '11 at 14:59
  • So will boost::optional perform a copy constructor? If so, that kind of defeats the whole point here of not copying the rectangle and allowing the function to modify it but caller should consider it trashed upon return from that function. – WilliamKF Dec 14 '11 at 19:06
  • @WilliamKF : `boost::optional<>` uses placement new to avoid freestore allocations. – ildjarn Dec 14 '11 at 22:06
0

In our example, how would scaling the rectangle make it invalid? If you mutate the object in-place, it should remain valid, otherwise you operation is not useful.

As a caller, when passing an object to a function by non-const reference you should always expect side-effects.

The only reason where an object would become truly invalid is if you move from it. In that case the caller has to explicitly pass it using std::move, so they know that the object is not valid afterwards.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • Caller could be in a different frame of reference than the function that modifies it. So if function modifies object to be in its frame of reference, it is no longer valid for caller. – WilliamKF Dec 14 '11 at 12:55
0

I would [want to] have some way in the declaration of the function call to indicate that the memory passed in by reference should be considered invalid upon return from the function and have the compiler give an error if the caller tries to use it subsequent to the function call returning.

I think you are confusing a few things here.

For one, you do not pass a blob of memory to a function, but an object (of the type rectangle). An object is more than a blob of memory.
An object has had its constructor run, turn a blob of memory into an object, and setup the object to some definitive state. When the object dies, its destructor will run and turn the object into a blob of memory again. In between those two, it's an object.
To render an object invalid you need to either set one or more flags that are part of the object's state and signal invalidation (IO streams do that), or you need to call its destructor — which (usually) you should only do indirectly, either by deleteing and object created using new, or implicitly by letting it fall out of its scope (for automatic objects).

That leaves functions to change objects passed to them.
A function indicates the possibility that it might change an argument by its signature, and looking at how an object is passed to a function you see whether the function might change it. If you need to call a function and you need to pass an object that must, for some reason, not be changed as an argument for which the function indicates that it might change, you need to pass a copy of that object, rather than the real thing.

If you want to prevent an object from being used after it was used as a function argument for some reason I haven't touched here, you can simply put it into an small enclosing scope:

void f()
{
  g();
  {
    rectangle rect(...);
    h(r);
  } // rect dies here
  // rect not accessible here
  g();
}
Community
  • 1
  • 1
sbi
  • 219,715
  • 46
  • 258
  • 445