Is it possible to take a parameter by const refere

2020-04-06 07:24发布

问题:

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.)

回答1:

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.
}


回答2:

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.



回答3:

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.



回答4:

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.



回答5:

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) { }
};


回答6:

(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



标签: c++ const