is it possible to restrict class instances to be used only as temporaries?
Asked Answered
J

7

12

is it possible to restrict class instances to be used only as rvalues (e.g. temporaries)?

for example, I have class Wrapper whose constructor takes A const& and saves this reference in its member. It's a dangerous because lifetime of Wrapper instance cannot be longer than lifetime of A instance, but it's fine if Wrapper is temporary.

Jumper answered 31/1, 2011 at 12:10 Comment(4)
Do you mean to only allow instances to be created on the stack rather than the heap?Forgotten
nope, only as temporaries. it's still dangerous to create such instances on the stackJumper
Sometimes the best preventative is a comment of "don't do X" in the documentation.Foreshorten
Scott Meyers has a paper out there somewhere (plus it's in Effective C++, I think) where the title is "Make interfaces easy to use correctly and hard to use incorrectly" - or something to this effect, anyway. I think your design violates that rule of thumb.Halvaard
P
4

I don't think it would be safe:

const A &a = YourClass( tmp );

YourClass in this case is the class you're looking for which only allow temporary instances, tmp is the temporary value you pass to the constructor.
It's possible (ie: safe, defined behavior) to have a constant reference to a temporary (ie: a), but the temporary itself (such instance of YourClass) has got a reference to tmp which is no longer valid after that expression is evaluated.

Penneypenni answered 31/1, 2011 at 12:19 Comment(1)
Exactly what I was thinking. You can do it syntactically but the fact you can bind const-references to it will extend its lifetime anyway in a dangerous way.Diatonic
S
5

I think that even wanting to do this is a sign of a really bad design.

However, you could make all constructors private and make a friend function that returns an rvalue. That should do the trick.

Saber answered 31/1, 2011 at 12:40 Comment(3)
How would this work? Either the copy ctor or move ctor must be accessible to return by value, even if they are elided.Foreshorten
Not if he's taking the result by reference.Saber
Even when taking the result by reference: codepad.org/97oaJNfl and N3225 §12.2p1, "…binding a reference to a prvalue, returning a prvalue… Even if the copy/move constructor is not called, all the semantic restrictions, such as accessibility, shall be satisfied."Foreshorten
P
4

I don't think it would be safe:

const A &a = YourClass( tmp );

YourClass in this case is the class you're looking for which only allow temporary instances, tmp is the temporary value you pass to the constructor.
It's possible (ie: safe, defined behavior) to have a constant reference to a temporary (ie: a), but the temporary itself (such instance of YourClass) has got a reference to tmp which is no longer valid after that expression is evaluated.

Penneypenni answered 31/1, 2011 at 12:19 Comment(1)
Exactly what I was thinking. You can do it syntactically but the fact you can bind const-references to it will extend its lifetime anyway in a dangerous way.Diatonic
P
3

Not exactly the answer you are looking for, but have you thought about weak pointers? (for example, boost::weak_ptr). In this case, the original A would be held in a shared_ptr and the Wrapper constructor accepts a weak_ptr. The neat thing with this approach is that, before each usage of the weak_ptr, you can attempt to lock() which will give you a shared_ptr - if that fails, you know that A is gone and Wrapper cannot function... But it's handled cleanly...

Platina answered 31/1, 2011 at 12:41 Comment(0)
F
3

This might do the job unless your class has public data members.

Basically, the idea is not to restrict the construction of the wrapper but to make sure that instances can be used (just like you said) only as long as they are temporary values. One can achieve this by overloading all methods and deleting (or making them private) those that refer to const&.

Here's a simple example:

class Wrapper
{
public:
    Wrapper() = default;
    Wrapper(const std::string& name) : name(name) {}
    void process() && { std::cout << "Greetings from " << name << std::endl; }
    // Only temporary instances of this class are allowed!
    void process() const & = delete;

private:
    std::string name;
};

And some use cases:

Wrapper("John").process(); // intended use case
Wrapper j; // create whatever you want
j.process();  // error C2280: 'void Wrapper::process(void) const &': attempting to reference a deleted function
std::move(j).process(); // this is still possible
const Wrapper& t = Wrapper();  // bind the temporary to a const reference - not a problem because ...
t.process(); // error C2280: 'void Wrapper::process(void) const &': attempting to reference a deleted function

The obvious disadvantages are:

  • You have to overload every public member function.
  • The error message is delayed and not very informative.

A similar thing has been done in the standard. The make routines for std::reference_wrapper do not accept temporaries.

Note that they considered another subtlety: the overload uses const T&& instead of T&&. This can be important in our case as well. For example, if your wrapper is deliberately designed to be noncopyable and you use make routines such as

const Wrapper make_wrapper();

instead of

Wrapper make_wrapper();

In this case, you might want to replace

void process() &&;

by

void process() const &&;
Freddie answered 19/2, 2017 at 13:0 Comment(0)
A
1

I'd not bother enforcing this at compile time, as there are always going to be corner cases where this would be overly restrictive, limiting the usefulness of the class, but rather wrap tools like valgrind or Purify so I can spot places where invalidated references are used.

Axe answered 31/1, 2011 at 12:37 Comment(0)
E
1

I believe in C++17 and later you can get approximately what you want by doing the following:

  1. Delete the move constructor for your type (and don't define a copy constructor).

  2. Always accept your type by value in APIs.

So, for example:

#include <type_traits>
#include <utility>

// A non-moveable, non-copyable type.
struct CantMove {
  CantMove(CantMove&&) = delete;
  CantMove(int) {}  // Some other constructor
};

static_assert(!std::is_move_constructible_v<CantMove>);
static_assert(!std::is_copy_constructible_v<CantMove>);

// A function that accepts it by value.
bool AcceptByValue(CantMove input) { return true; }

// It's possible to call the value-accepting API when the input is a prvalue
// (which in previous versions of C++ would have been a temporary).
bool unused = AcceptByValue(CantMove(0));

// But it's not possible to call with a named value, even when casted to an
// rvalue reference. This doesn't compile.
CantMove cant_move(0);
bool unused_2 = AcceptByValue(std::move(cant_move));

It's possible to provide the value-accepting function with what we previously called a temporary because guaranteed copy elision says that there isn't even a temporary involved anymore—the only CantMove object created is the function parameter itself, so there is no move- or copy-construction involved. In contrast it's not possible to call with std::move(cant_move) because that would involve move-constructing the function parameter, and the type is not move-constructible.

Of course it's still possible to initialize a CantMove directly:

CantMove foo{0};

But if you own all of the APIs that accept a CantMove and make them all accept by value, then you can't actually do anything with foo afterward. This means it would be hard for a user to do this by mistake and not realize the problem.

Electrolysis answered 9/5, 2022 at 8:41 Comment(0)
D
0

Yes, you could.

You would make the constructor and regular copy-constructor/assign private but make the r-value move semantics (C++0x) public.

You would have a static or friend constructor to create the temporary.

In 2003 C++ you would also be able to use this to bind to a const reference.

Of course you'd have the issue that your const reference would probably become invalidated after the statement.

Diatonic answered 31/1, 2011 at 12:18 Comment(6)
Uhm... Are you sure that is going to work? If the only public constructor is the rvalue reference one, I don't think you'll be able to instantiate any instance of your class - thus you will never be able to pass an instance to the rvalue reference constructor..Penneypenni
He was suggesting using a friend function that returns an rvalue.Saber
This doesn't prevent T const &ref = T::factory(blah);: move semantics allow a return-by-value (which can be elided) and the const& extends the lifetime. What am I missing?Foreshorten
@Fred Nurk: I don't think you've missed anything, this broken "binding to const" rule really is annoying :/Beldam
@Fred I did remark that although it could be done you would not be totally safe with it for the reason I stated.Diatonic
@CashCow: Wouldn't that be "no, you can't, but you can get somewhat close" instead of "yes, you could"?Foreshorten

© 2022 - 2024 — McMap. All rights reserved.