Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

prevent pass-by-ref of temporary object

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?

like image 664
xtofl Avatar asked Jun 20 '12 20:06

xtofl


3 Answers

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{}};
like image 152
Tomilov Anatoliy Avatar answered Nov 17 '22 00:11

Tomilov Anatoliy


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;
like image 43
Jonathan Wakely Avatar answered Nov 17 '22 00:11

Jonathan Wakely


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

like image 2
hsanders Avatar answered Nov 17 '22 01:11

hsanders