aka. how do I prevent a const& parameter from accidentally binding to a temporary?
We have a class that essentially looks like this:
template<typename T>
struct Observer {
const T* target;
void observe(const T& x) {
target = &x;
}
};
That is, these objects will "hold on" to the const-ref they get passed.
We had a bug where the observe function accidentally got passed a temporary object - something that can never be valid for this case.
What options do we have to prevent accidentally binding to a temporary?
Changing the signature to (const T* px) would work technically (taking the address of a temporary is a compiler warning=error here) it is not very attractive for other reasons.
Side note: Yeah, this will always have potential lifetime management issues, but so far the real thing worked pretty well because of its limited usage pattern - but passing temporaries accidentally has been observed in practice, so we want to possibly address that first.
You can add a rvalue reference overload, and delete it:
void observe(const T&& x) = delete;
Now a compile error will be issued if someone tries to pass a temporary.
Related
I have a function which accepts a Large by const reference:
void func(const Large& param);
and a class which holds a Large:
class HoldsLarge {
public:
Large GetByValue() const { return l; };
private:
Large l;
}
If I do
HoldsLarge x;
func(x.GetByValue());
am I correct in understanding that a temporary will be copy constructed for x.GetByValue() which will by passed by reference to func? Is there something in the standard which will allow a compiler to omit the construction of the temporary altogether? After all, func only need a const reference to HoldsLarge::l.
I understand I could simply return HoldsLarge::l by const reference but I would like to prevent clients from accidentally creating a dangling reference.
Compiler is allowed to change behavior (as optimization) in few cases: for NRVO, and since C++14 for new expression.
You are not in those cases.
Then; as-if rule allows any optimizations as long as observable behavior is identical.
So compiler can do the optimization only if that doesn't change the behavior.
Without knowing func, it cannot safely do that.
func might have access to x.l (or alias) from another way (as global).
For example, following func would prohibit the change.
Large* largePtr; // possibly set to &x.l by any way
void func(const Large& param)
{
print(param.x);
//param might be alias of largePtr
mutate(alias->x);
print(param.x); // const ref should see the modification
// whereas copy don't
}
I wrote a function which has this form:
Result f( const IParameter& p);
My intention is that this signature will make it clear that the function is not taking ownership of the parameter p.
Problem is that Result will keep a reference to IParameter:
class Result
{
const IParameter& m_p;
public:
Result( const IParameter& p )
: m_p( p ){ }
};
But then it happened that somebody called the function like this:
const auto r = f(ConcreteParameter{});
Unfortunately the temporary can be bound to const reference, and this caused a crash.
Question is: how can I make it clear that the function is not supposed to be called with temporaries, and maybe have a nice compilation error when that happens? Is it actually wrong in this case to state that it is not taking the ownership, since is passing it to result that will be propagated outside the function call scope?
The easiest way to make it clear is to overload the function with an rvalue reference parameter. Those are prefered to const references for temporaries so they will be chosen instead. If you then delete said overload, you'll get a nice compiler error. For your code that would look like:
Result f( const IParameter&& ) = delete;
You can also do the same thing with Result to gaurd it as well and that would look like:
class Result
{
const IParameter& m_p;
public:
Result( const IParameter& p )
: m_p( p ){ }
Result( const IParameter&& ) = delete;
};
In general, if a function receives value by const&, it's expected, that the function will use the value, but won't hold it. You do hold the reference to value so you should probably change argument type to use shared_ptr (if the resource is mandatory) or weak_ptr (if resource is optional). Otherwise you'll run into that kind of problems from time to time, as no one reads documentation.
It's hard to tell. The best way would be to document that Result must not live longer than the IParameter used to construct it.
There are valid cases of temporaries sent as constructor that is perfectly valid. Think about this:
doSomethingWithResult(Result{SomeParameterType{}});
Deleting the constructor taking temporaries would prevent such valid code.
Also, deleting the rvalue constructor won't prevent all cases. Think about this:
auto make_result() -> Result {
SomeParameterType param;
return Result{param};
}
Even if the constructor with temporary is deleted, invalid code is still really easy to make. You will have to document the lifetime requirement of your parameters anyways.
So if you have to document such behavior anyways, I would opt for what the standard library does with string views:
int main() {
auto sv = std::string_view{std::string{"ub"}};
std::cout << "This is " << sv;
}
It won't prevent constructing string views from temporary strings since it can be useful, just like my first example.
You can manually remove a constructor accepting an IParameter&& rvalue from the overload set:
class Result
{
// ...
public:
Result( IParameter&& ) = delete; // No temporaries!
Result( const IParameter& p );
};
When client code tries to instantiate an object via
Result f(ConcreteParameter{}); // Error
the constructor taking a const-qualified reference is no match because of the missing const-ness, but the rvalue constructor exactly matches. As this one is = deleted, the compiler refuses to accept such an object creation.
Note that as pointed out in the comments, this can be circumvented with const-qualified temporaries, see #NathanOliver's answer for how to make sure this doesn't happen.
Also note that not everyone agrees that this is good practice, have a look here (at 15:20) for example.
I already voted #NathanOliver answer as the best one, because I really think it is given the information I provided. On the other hand I would like to share what I think is a better solution to solve this very specific scenario when the function is more complex than the one in my initial example.
The problem with the delete solution is that it grows exponentially with the number of parameters, assuming that all the parameters needs to stay alive after the function call ends and you want a compile time check that the user of your API is not trying to give the ownership of those parameters to the function:
void f(const A& a, const B& b)
{
// function body here
}
void f(const A& a, B&& b) = delete;
void f(A&& a, const B& b) = delete;
void f(A&& a, B&& b) = delete;
We need to delete all the possible combination, and this will be hard to maintain on the long run. So my proposed solution is to take advantage of the fact that the reference_wrapper constructor which wraps T by move is already deleted in STD, and then write this:
using AConstRef = reference_wrapper<const A>;
using BConstRef = reference_wrapper<const B>;
void f(AConstRef a, BConstRef b)
{
// function body here
}
In this way all the invalid overload will be automatically deleted. I do not see any drawback with this approach so far.
Consider this class
class Foo
{
public:
Foo()
: r_obj(&nullobj)
{}
void setDataRef(const T& obj)
{
r_obj = &obj;
}
private:
static T nullobj;
const T* r_obj;
};
This forces r_obj to point to something, and I know that the class cannot modify the logical state of whatever it points to. However, now it is perfectly legal to pass a temporary object to setDataRef which is really bad.
If instead using setDataRef(T&), the constness is lost.
Passing a pointer does not force the object to really point to something.
Is it possible to get all three:
Constness
Non-null
Forbid temporary
In old C++ I think this was impossible, but with C++11 we can do:
class Foo
{
public:
Foo()
: r_obj(&nullobj)
{}
void setDataRef(const T& obj)
{
r_obj = &obj;
}
void setDataRef(T&& obj) = delete; // Crucial extra line
private:
static T nullobj;
const T* r_obj;
};
If somebody tries to pass a temporary to setDataRef, overload resolution will prefer the T&& version but because it is deleted, the call is ill-formed. If somebody passes an lvalue, that will bind to the const reference.
Martin's suggestion is very nice and does prevent the caller from passing an rvalue to the function. But that still won't guarantee that the lifetime of the passed object extends beyond the lifetime of the Foo instance.
If the object stores a pointer to the passed object, it would be most intuitive, if a pointer were passed to the setter. Passing a pointer also prevents using an rvalue (because taking an address of an rvalue is not allowed).
Just like you have to document the interface that the passed object must exist as long as Foo instance exists, you can also document that the passed pointer must not be null. Additionally, you can assert it. You could similarly delete the overload for nullptr_t to prevent passing the literal to the setter but that won't prevent passing null pointers of T* type. In my opinion, the advantage of the reference setter is not great.
Of course, neither raw pointer, nor a reference tell the caller anything about ownership, so that must in either case be documented. Instead, I would recommend storing and passing a smart pointer. unique_ptr if Foo should have the ownership (apparently not), shared_ptr if Foo should keep the object alive if it goes out of scope elsewhere, or possibly weak_ptr if Foo should have a failure mode when referred object no longer exists. A smart pointer would be self documenting and intuitive for the user.
Sometimes we like to take a large parameter by reference, and also to make the reference const if possible to advertize that it is an input parameter. But by making the reference const, the compiler then allows itself to convert data if it's of the wrong type. This means it's not as efficient, but more worrying is the fact that I think I am referring to the original data; perhaps I will take it's address, not realizing that I am, in effect, taking the address of a temporary.
The call to bar in this code fails. This is desirable, because the reference is not of the correct type. The call to bar_const is also of the wrong type, but it silently compiles. This is undesirable for me.
#include<vector>
using namespace std;
int vi;
void foo(int &) { }
void bar(long &) { }
void bar_const(const long &) { }
int main() {
foo(vi);
// bar(vi); // compiler error, as expected/desired
bar_const(vi);
}
What's the safest way to pass a lightweight, read-only reference? I'm tempted to create a new reference-like template.
(Obviously, int and long are very small types. But I have been caught out with larger structures which can be converted to each other. I don't want this to silently happen when I'm taking a const reference. Sometimes, marking the constructors as explicit helps, but that is not ideal)
Update: I imagine a system like the following: Imagine having two functions X byVal(); and X& byRef(); and the following block of code:
X x;
const_lvalue_ref<X> a = x; // I want this to compile
const_lvalue_ref<X> b = byVal(); // I want this to fail at compile time
const_lvalue_ref<X> c = byRef(); // I want this to compile
That example is based on local variables, but I want it to also work with parameters. I want to get some sort of error message if I'm accidentally passing a ref-to-temporary or a ref-to-a-copy when I think I'll passing something lightweight such as a ref-to-lvalue. This is just a 'coding standard' thing - if I actually want to allow passing a ref to a temporary, then I'll use a straightforward const X&. (I'm finding this piece on Boost's FOREACH to be quite useful.)
Well, if your "large parameter" is a class, the first thing to do is ensure that you mark any single parameter constructors explicit (apart from the copy constructor):
class BigType
{
public:
explicit BigType(int);
};
This applies to constructors which have default parameters which could potentially be called with a single argument, also.
Then it won't be automatically converted to since there are no implicit constructors for the compiler to use to do the conversion. You probably don't have any global conversion operators which make that type, but if you do, then
If that doesn't work for you, you could use some template magic, like:
template <typename T>
void func(const T &); // causes an undefined reference at link time.
template <>
void func(const BigType &v)
{
// use v.
}
If you can use C++11 (or parts thereof), this is easy:
void f(BigObject const& bo){
// ...
}
void f(BigObject&&) = delete; // or just undefined
Live example on Ideone.
This will work, because binding to an rvalue ref is preferred over binding to a reference-to-const for a temporary object.
You can also exploit the fact that only a single user-defined conversion is allowed in an implicit conversion sequence:
struct BigObjWrapper{
BigObjWrapper(BigObject const& o)
: object(o) {}
BigObject const& object;
};
void f(BigObjWrapper wrap){
BigObject const& bo = wrap.object;
// ...
}
Live example on Ideone.
This is pretty simple to solve: stop taking values by reference. If you want to ensure that a parameter is addressable, then make it an address:
void bar_const(const long *) { }
That way, the user must pass a pointer. And you can't get a pointer to a temporary (unless the user is being terribly malicious).
That being said, I think your thinking on this matter is... wrongheaded. It comes down to this point.
perhaps I will take it's address, not realizing that I am, in effect, taking the address of a temporary.
Taking the address of a const& that happens to be a temporary is actually fine. The problem is that you cannot store it long-term. Nor can you transfer ownership of it. After all, you got a const reference.
And that's part of the problem. If you take a const&, your interface is saying, "I'm allowed to use this object, but I do not own it, nor can I give ownership to someone else." Since you do not own the object, you cannot store it long-term. This is what const& means.
Taking a const* instead can be problematic. Why? Because you don't know where that pointer came from. Who owns this pointer? const& has a number of syntactic safeguards to prevent you from doing bad things (so long as you don't take its address). const* has nothing; you can copy that pointer to your heart's content. Your interface says nothing about whether you are allowed to own the object or transfer ownership to others.
This ambiguity is why C++11 has smart pointers like unique_ptr and shared_ptr. These pointers can describe real memory ownership relations.
If your function takes a unique_ptr by value, then you now own that object. If it takes a shared_ptr, then you now share ownership of that object. There are syntactic guarantees in place that ensure ownership (again, unless you take unpleasant steps).
In the event of your not using C++11, you should use Boost smart pointers to achieve similar effects.
You can't, and even if you could, it probably wouldn't help much.
Consider:
void another(long const& l)
{
bar_const(l);
}
Even if you could somehow prevent the binding to a temporary as input to
bar_const, functions like another could be called with the reference
bound to a temporary, and you'd end up in the same situation.
If you can't accept a temporary, you'll need to use a reference to a
non-const, or a pointer:
void bar_const(long const* l);
requires an lvalue to initialize it. Of course, a function like
void another(long const& l)
{
bar_const(&l);
}
will still cause problems. But if you globally adopt the convention to
use a pointer if object lifetime must extend beyond the end of the call,
then hopefully the author of another will think about why he's taking
the address, and avoid it.
I think your example with int and long is a bit of a red herring as in canonical C++ you will never pass builtin types by const reference anyway: You pass them by value or by non-const reference.
So let's assume instead that you have a large user defined class. In this case, if it's creating temporaries for you then that means you created implicit conversions for that class. All you have to do is mark all converting constructors (those that can be called with a single parameter) as explicit and the compiler will prevent those temporaries from being created automatically. For example:
class Foo
{
explicit Foo(int bar) { }
};
(Answering my own question thanks to this great answer on another question I asked. Thanks #hvd.)
In short, marking a function parameter as volatile means that it cannot be bound to an rvalue. (Can anybody nail down a standard quote for that? Temporaries can be bound to const&, but not to const volatile & apparently. This is what I get on g++-4.6.1. (Extra: see this extended comment stream for some gory details that are way over my head :-) ))
void foo( const volatile Input & input, Output & output) {
}
foo(input, output); // compiles. good
foo(get_input_as_value(), output); // compile failure, as desired.
But, you don't actually want the parameters to be volatile. So I've written a small wrapper to const_cast the volatile away. So the signature of foo becomes this instead:
void foo( const_lvalue<Input> input, Output & output) {
}
where the wrapper is:
template<typename T>
struct const_lvalue {
const T * t;
const_lvalue(const volatile T & t_) : t(const_cast<const T*>(&t_)) {}
const T* operator-> () const { return t; }
};
This can be created from an lvalue only
Any downsides? It might mean that I accidentally misuse an object that is truly volatile, but then again I've never used volatile before in my life. So this is the right solution for me, I think.
I hope to get in the habit of doing this with all suitable parameters by default.
Demo on ideone
I do not understand what is going on. I am just learning C++ and I see something like this a lot:
double some_function(const Struct_Name& s) {
...
}
Why the const if we are passing by reference?
You pass by const reference when you don't want to(or can't) modify the argument being passed in, and you don't want the performance hit that you might get from copying the object.
A const reference prevents the object from being modified, as const would anywhere else, but also avoids the potential cost of copying.
You are telling the compiler you're not going to change s, ever.
This enables it to make some optimizations it wouldn't have been able to do otherwise. Basically, it gives you the same semantics as passing by value, but doesn't incur the performance penalty of calling the copy constructor.
Call by const-reference avoids a copy of the Struct_Name while promising not to modify it.
There is both a performance reason for this, and a semantics reason.
If Struct_Name is large, copying it is expensive in both time and memory.
If Struct_Name is uncopyable (or becomes invalid when copied) calling by value is impossible or introduces undesirable complexity. For example: std::unique_ptr and std::auto_ptr.
By using const we can signal both the user of the function and the compiler that the object passed as the argument s will not be changed inside the function (which would actually be possible, because we pass it by reference!). The compiler can than give us an error if we modify the object by accident and it can do some optimizations it couldn't do otherwise.
An additional advantage is, that if the caller of the function only owns a const pointer to an object, it can still provide that object as an argument without casting.
const here promisses that some_function will not modify s parameter,
double some_function(const Struct_Name& s) {
...
}
you can try modifying it but compiler will return errors. Actually constness requires you to carefully write Struct_Name internal methods, ie. you will not be able to call inside some_function non-const functions on s object. You can try, but you will get error. ie:
struct Struct_Name {
void myfun() const { } // can be called from some_function
void myfun2() { } // will show error if called from some_function
};
Using const parameter is good from design point of view, if you know some function is not supposed to change your object then you add const. This means that no other programmer can do changes in some deeply hidden in classes hierarchy code, that will modify your object. It really makes debugging easy.
Another reason that noone has mentioned yet - passing by a const reference allows the compiler to create and pass a temporary object, without generating a warning, if the input value is not the exact type declared in the parameter, but the type has a constructor that supports the type being passed in.
For example:
void foo(const std::string &s)
{
...
}
foo("hello"); // OK
foo() is expecting a std::string but receives a const char* instead. Since std::string has a constructor that accept a const char*, the compiler generates code that is effectively doing this:
std::string temp("hello");
foo(temp);
The compiler knows the parameter is const, the temporary will not be altered by foo(), and the temporary will be discarded after foo() exits, so it does not complain about having to create a temporary.
The same thing happens if the parameter is passed by value (const or non-const, it does not matter) instead of by reference:
void foo(const std::string s)
{
...
}
void bar(std::string s)
{
...
}
foo("hello"); // OK
bar("world"); // OK
This is effectively the same as this:
{
std::string temp1("hello");
foo(temp1);
}
{
std::string temp2("world");
bar(temp2);
}
Again, the compiler does not complain, as it knows the temporary does not affect the calling code, and any alterations made to the temporary in bar() will be safely discarded.
If the parameter were a non-const reference instead, passing a const char* would generate a warning about the temporary that has to be created to satisfy the reference binding. The warning is to let you know that any changes the function makes to the temporary (since it is not const) will be lost when the function exits, which may or may not have an effect on the calling code. This is usually an indication that a temporary should not be used in that situation:
void foo(std::string &s)
{
...
}
foo("hello"); // warning!