简体   繁体   English

有没有办法通过引用传递,并在函数调用中显式传递值?

[英]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. 您将无法通过任何语法方式验证参数x是通过引用传递还是通过值传递。 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. 我意识到还有一个选择传递const&,它具有类似于传递值的概念,因为传入的变量在函数调用之后不会更改其值。


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. ......显然,实现Lowercase()以获取指针。 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()实际上是一个算法,并且可以合理地称之为具有一点魔力

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* . 通过指针仍然不指示参数是否可以被修改,因为参数可能是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. 这是C ++:缺少inout参数并不意味着语言不足,这意味着您需要实现其他语言作为库的语言功能。

Create two template classes and functions. 创建两个template类和函数。

in_param<T> is a wrapper around a T const& , whilie io_param<T> is a wrapper around a T& reference. in_param<T>是一个围绕T const&的包装器,whilie io_param<T>T& reference的包装器。 You construct them by calling helper functions in and io . 您可以通过调用辅助函数构造它们inio

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. 在外面,呼叫者必须in参数上打电话或者io ,在呼叫站点上标记它。

out is trickier: inside the fumction, only assignment is legal. out更棘手:在内部,只有作业是合法的。 Ideally we would not even construct it: an emplace method might help. 理想情况下,我们甚至不会构造它:一个emplace方法可能会有所帮助。

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_param只有operator= ,并且它会分配。 out wraps something into an out_param . out东西包装成out_param If you want delayed constructuon, use optional inside the out param, which gets close. 如果你想要延迟构造,请在out param中使用optional ,它会接近。 Maybe out_param also has emplace , which usually just assigns, but if the tyoe wrapped has emplace calls it instead? 也许out_param也有emplace ,一般只分配,但是如果包裹tyoe具有emplace而不是调用呢?

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. 和失败调用inioout在调用点导致编译时错误。 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. 另外, out_param使得很难意外地读取函数中out变量的状态,在调用站点生成一些非常好的文档。

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

I think it's something useless to notify (by language nonetheless [1]). 我认为这是无用的(通过语言[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). 当你读取原型时,你知道一个函数是否可以修改一个对象(非const引用)或者不是(复制或const引用)。
  • 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. 当您使用函数(即使不读取[2]原型)时,如果您必须确保不修改对象,请使用const_cast。

[1] A static analyzer could do it for its purposes. [1]静态分析仪可以用于其目的。
[2] If you miss, the compiler would warn you anyway. [2]如果你错过了,编译器会警告你。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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