简体   繁体   中英

Is there a way to make passing by reference, and passing by value explicit in the function call?

If you were to look at this code,

int x = 0;
function(x);
std::cout << x << '\n';

you would not be able to verify through any means of syntax, that parameter x is being passed by reference or that it's being passed by value. The only way you would know for sure, is if you looked at either the function declaration or function definition.

Here is a simple example of how I believe this could be a problem:

std::string Lowercase(std::string str); //<- this is hidden away in code; probably in a different file.

int main(){
    std::string str = "HELLO";
    Lowercase(str);
    std::cout << str << '\n'; //<- Bug! we expected to output "hello".  The problem is not very easy to spot, especially when a function name sounds as though it will change the passed in value.
}

In order to avoid having to jump between the function call and the function declaration (or in some cases, the documentation) in order to understand the function behavior, is there a way to explicitly document in the syntax of a function call that the parameter is expected to change (ie a reference parameter) or that a copy is being sent (ie pass by value)?

I realize that there is also the option of passing by const& which has the similar notion to passing by value, in that the variable passed in, will not have its value changed after the function call.


I'm sure there are all kinds of situations in the language that might add to the complexity of understanding how a parameter is being passed- but I'm curious, is there a way to combat this problem in the way I want to?

I've noticed that some people write two similar functions. One of them takes a value parameter, the other one takes a pointer. That allows calling a function like this:

Lowercase(str); //we assume the value will not change
Lowercase(&str); //we assume the value will change

But this solution has many other issues, and I would not like to lose the benefit of references. Plus, we are still making assumptions on the behavior.

Some people insist that the correct way to pass mutable object is to use a pointer. That is, you would pass

Lowercase(&str);

... and Lowercase() would, obviously, be implemented to take a pointer. That approach may suit your needs.

I want to mention, however, that this is not what I would do! Instead, the approach I favor is to use appropriate names instead. For example,

inplace_lowercase(str);

pretty much says what it is going to do. Clearly, inplace_lowercase() would actually be an algorithm and with a bit of magic could be reasonably be called as

inplace_lowercase(str.begin() + 1, str.end());

as well.

Here are a few reasons why I don't like passing arguments by pointer and/or why I don't believe in an explicit indication of how the argument is passed:

  • Pointers can be null. A mandated reference parameters should, in my opinion, be a reference.
  • Passing by pointer still doesn't indicate whether the argument may be modified are not as the argument may be a T const* .
  • Having meaningful names makes it actually easier to understand what's going on in the first place.
  • Calling something without consulting its documentation and/or knowing what the called function will do doesn't work anyway and indicating how things are passed is trying to cure symptoms of a deeper problem.

I'm not sure I understand your requirements completely, but maybe this is something you can use:

template<typename T>
void foo( T ) { static_assert( sizeof(T)==0, "foo() requires a std::ref" ); }

void foo( std::reference_wrapper<int> t )
{
    // modify i here via t.get() or other means of std::reference_wrapper
}

int main()
{
    int i = 42;
    // foo( i ); // does not compile, static_assert fires
    foo( std::ref( i ) ); // explicit std::ref visible on the caller's side
}

许多(大多数)IDE通过在找出您正在调用的函数时显示函数/方法原型来帮助您解决此问题。

This is C++: the lack of in and out parameters doesn't mean the language is deficient, it means you need to implement what other languages would do as a language feature as a library.

Create two template classes and functions.

in_param<T> is a wrapper around a T const& , whilie io_param<T> is a wrapper around a T& reference. You construct them by calling helper functions in and io .

Inside, they behave like references (via overloading).

Outside, the caller must call in or io on the argument, marking it up at the call site.

out is trickier: inside the fumction, only assignment is legal. Ideally we would not even construct it: an emplace method might help.

However, the caller needs some channel to know if the parameter was constructed or not.

What I would do is out_param only has operator= , and it assigns. out wraps something into an out_param . If you want delayed constructuon, use optional inside the out param, which gets close. Maybe out_param also has emplace , which usually just assigns, but if the tyoe wrapped has emplace calls it instead?

template<typename T>
struct in_param : std::reference_wrapper<T const> {
  explicit in_param( T const& t ):std::reference_wrapper<T const>(t) {}
  in_param( in_param<T>&& o ):std::reference_wrapper<T const>(std::move(o)) {}
  void operator=( in_param<T> const& o ) = delete;
};
template<typename T>
struct io_param : std::reference_wrapper<T> {
  explicit io_param( T& t ):std::reference_wrapper<T>(t) {}
  io_param( io_param<T>&& o ):std::reference_wrapper<T>(std::move(o)) {}
};
template<typename T>
in_param< T > in( T const& t ) { return in_param<T>(t); }
template<typename T>
io_param< T > io( T& t ) { return io_param<T>(t); }

template<typename T>
struct out_param {
private:
  T& t;
public:
  out_param( T& t_ ):t(t_) {}
  out_param( out_param<T>&& o ):t(o.t) {}
  void operator=( out_param<T> const& o ) = delete;
  void operator=( out_param<T> && o ) = delete;
  void operator=( out_param<T> & o ) = delete;
  void operator=( out_param<T> && o ) = delete;
  template<typename U>
  out_param<T>& operator=( U&& u ) {
    t = std::forward<U>(u);
    return *this;
  }
  // to improve, test if `t` has an `emplace` method.  If it does not,
  // instead do t = T( std::forward<Us>(us)... ). (I'd use tag dispatching
  // to call one of two methods)
  template<typename... Us>
  void emplace( Us&&... us ) {
    t.emplace( std::forward<Us>(us)... );
  }
};
template<typename T>
out_param<T> out( T& t ) { return out_param<T>(t); }

or something like the above.

You now get syntax like:

void do_stuff( int x, in_param<expensive> y, io_param<something> z, out_param<double> d );

int main() {
  expensive a;
  something b;
  double d;
  do_stuff( 7, in(a), io(b), out(d) );
}

and failure to call in , io or out at the call site results in compile time errors. Plus, out_param makes it quite difficult to accidentally read the state of the out variable within the function, producing some very nice documentation at the call site.

如果你使用MS VC ++,那么它可能是关于源代码注释语言(SAL)的有用信息http://msdn.microsoft.com/ru-ru/library/hh916383.aspx

I think it's something useless to notify (by language nonetheless [1]). The only needed question is : "Is my object is semantically modified ?", and so :

  • When you read a prototype you know if a function could modify an object (non-const ref) or not (copy or const ref).
  • When you use a function (even without reading [2] the prototype) if you have to be sure to not modify an object, use a const_cast.

[1] A static analyzer could do it for its purposes.
[2] If you miss, the compiler would warn you anyway.

这是传递引用的全部要点 - 语法上不需要做任何与传递值不同的事情。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM