Is it possible to take a parameter by const reference, while banning conversions so that temporaries aren't passed instead?

后端 未结 6 1449
面向向阳花
面向向阳花 2021-01-18 21:58

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 refe

相关标签:
6条回答
  • 2021-01-18 22:13

    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.

    0 讨论(0)
  • 2021-01-18 22:22

    (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

    0 讨论(0)
  • 2021-01-18 22:27

    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.
    }
    
    0 讨论(0)
  • 2021-01-18 22:27

    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.

    0 讨论(0)
  • 2021-01-18 22:35

    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) { }
    };
    
    0 讨论(0)
  • 2021-01-18 22:37

    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.

    0 讨论(0)
提交回复
热议问题