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?
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{}};
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;
That shouldn't compile. A good C++ compiler (or really almost any C++ compiler that I've ever seen) will stop that from happening.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With