10

I have a class that 'remembers' a reference to some object (e.g. an integer variable). I can't have it reference a value that's destructed immediately, and I'm looking for a way to protect the users of my class from doing so by accident.

Is an rvalue-reference overload a good way to prevent a temporary to be passed in?

struct HasRef {
    int& a;
    HasRef(int& a):a(a){}
    void foo(){ a=1; }
};


int main(){
    int x=5;
    HasRef r1(x);
    r1.foo();  // works like intended.

    HasRef r2(x+4);
    r2.foo(); // dereferences the temporary created by x+4

 }

Would a private rvalue overload do?

 struct HasRef {
   int& a;
   HasRef( int& a ):a(a){}
   void foo(){ a=1; }
 private: 
   HasRef( int&& a );
 };

 ... HasRef r2(x+1); // doesn't compile => problem solved?

Are there any pitfalls I didn't see?

xtofl
  • 40,723
  • 12
  • 105
  • 192
  • 4
    A temporary doesn't bind to an lvalue reference. The definition of `r2` in your first example should not compile. – musiphil Jun 20 '12 at 20:41
  • If you are using VC++, one solution is to turn up the warning level and it will tell you when it doesn't work. – Bo Persson Jun 20 '12 at 20:42
  • 6
    However, a _const_ reference would bind to a temporary, so the question is still a very good one. I've considered this approach, but I'm still of the opinion that if a class is going to store a reference (or pointer) to the referenced object, it's better to take a pointer in the constructor, to make potential lifetime concerns a bit more obvious (when a constructor takes a pointer, usually it makes me think twice about what the object is going to do with it). – James McNellis Jun 20 '12 at 20:43
  • @musiphil,@Dave: indeed; I've mind-distilled this class from something from my day job which caused a crash in VS10, but ran smoothly in gcc4.4; will provide better code tomorrow. – xtofl Jun 20 '12 at 20:45
  • @JamesMcNellis: But if it's a const reference, then it extends the lifetime of the temporary, so it's no longer a problem. – Benjamin Lindley Jun 20 '12 at 20:50
  • 5
    @BenjaminLindley: No, it extends the lifetime of the temporary only until construction completes. After the object is constructed, `a` refers to a no-longer-existent object. – James McNellis Jun 20 '12 at 20:50
  • Of course, I should have said "A temporary doesn't bind to a non-const lvalue reference." :-) – musiphil Jun 20 '12 at 22:39
  • @xtofl, can you find other questions for which the [tag:pass-by-rvalue-reference] tag could apply? It seems exceptionally specific to this question only, and that's not really a good case for a new tag. – Charles Jun 21 '12 at 03:38
  • @Charles: it is, indeed, a new tag. It's a quite new concept, too. Maybe there are tags for 'forwarding' etc..., but I didn't try look it up. I'll dig into it. – xtofl Jun 21 '12 at 04:38

4 Answers4

7

If you have to store a const reference to some instance of type B into your class A, then surely you want to be ensured, that lifetime of A instance will be exceeded by the lifetime of B instance:

B b{};
A a1{b}; // allowed
A a2{B{}}; // should be denied
B const f() { return B{}; } // const result type may make sense for user-defined types
A a3{f()}; // should also be denied!

To make it possible you should explicitly to = delete; all the constructor overloadings, which can accept rvalues (both const && and &&). For this to achieve you should just to = delete; only const && version of constructor.

struct B {};

struct A
{
    B const & b;
    A(B const & bb) : b(bb) { ; } // accepts only `B const &` and `B &`
    A(B const &&) = delete; // prohibits both `B &&` and `B const &&`
};

This approach allows you to prohibit passing to the constructor all kinds of rvalues.

This also works for built-in scalars. For example, double const f() { return 0.01; }, though it cause a warning like:

warning: 'const' type qualifier on return type has no effect [-Wignored-qualifiers]

it still can has effect if you just = delete; only && version of constructor:

struct A
{
    double const & eps;
    A(double const & e) : eps(e) {} // binds to `double const &`, `double &` AND ! `double const &&`
    A(double &&) = delete; // prohibit to binding only to `double &&`, but not to `double const &&`
};

double const get_eps() { return 0.01; }

A a{0.01}; // hard error
A a{get_eps()}; // no hard error, but it is wrong!

For non-conversion constructors (i.e. non-unary) there is an issue: you may have to provide = delete;-d versions for all the combinatorically possible versions of constructors as follows:

struct A
{
    A(B const &, C const &) {}
    A(B const &&, C const &&) = delete;
    // and also!
    A(B const &, C const &&) = delete;
    A(B const &&, C const &) = delete;
};

to prohibit mixed-cases like:

B b{};
A a{b, C{}};
Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
  • Good point, the multi arg constructors! Probably it would make sense to make all params templated, and statically assert all are lvalue references somehow. That would prevent mix-explosion. – xtofl Dec 25 '16 at 16:29
  • @xtofl Simple `static_assert` is not a good choise, when you apply a type trait like `std::is_constructible` to `A`. You need to SFINAE-out bad combinations or to use *Concept Lite* if avaliable. – Tomilov Anatoliy Dec 25 '16 at 16:59
3

Ignoring the fact the code isn't valid and just answering the question about the private overload...

In C++11 I would prefer a deleted function to a private function. It's a bit more explicit that you really can't call it (not even if you're a member or friend of the class.)

N.B. if the deleted constructor is HasRef(int&&)=delete it will not be chosen here:

int i;
HasRef hr(std::forward<const int>(i));

With an argument of type const int&& the HasRef(const int&) constructor would be used, not the HasRef(int&&) one. In this case it would be OK, because i really is an lvalue, but in general that might not be the case, so this might be one of the very rare times when a const rvalue reference is useful:

HasRef(const int&&) = delete;
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
2

That shouldn't compile. A good C++ compiler (or really almost any C++ compiler that I've ever seen) will stop that from happening.

hsanders
  • 1,913
  • 12
  • 22
  • thanks; it seems that I have to take a better look at my code and rephrase the problem. – xtofl Jun 20 '12 at 20:50
0

I'm guessing you're compiling in MSVS. In that case, turn off language extensions and you should get an error.

Otherwise, not that even marking the reference const extends the lifetime of the temporary until the constructor finishes. After that, you'll refer to an invalid object.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625