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

时间:2013-11-05 23:05:07

标签: c++ c c++11 parameters parameter-passing

如果您要查看此代码,

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

您将无法通过任何语法方式验证参数x是通过引用传递还是通过值传递。您可以肯定的唯一方法是查看函数声明或函数定义。

以下是我认为这可能是一个问题的一个简单示例:

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

为了避免必须在函数调用和函数声明(或在某些情况下,文档)之间跳转以便理解函数行为,有没有办法在函数调用的语法中显式地记录参数预计会改变(即参考参数)或 copy 正在发送(即按值传递)?

我意识到还有通过const&amp; amp;它具有与传递值相似的概念,因为传入的变量在函数调用后不会更改其值。


我确信语言中有各种各样的情况可能会增加理解参数如何传递的复杂性 - 但我很好奇,有没有办法以我想要的方式解决这个问题?

我注意到有些人写了两个类似的功能。其中一个采用值参数,另一个采用指针。这允许调用这样的函数:

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

但是这个解决方案还有很多其他问题,我不想失去参考的好处。另外,我们仍在对行为做出假设。

7 个答案:

答案 0 :(得分:23)

有些人坚持认为传递可变对象的正确方法是使用指针。也就是说,你会传递

Lowercase(&str);
显然,

...和Lowercase()将被实现为获取指针。这种方法可能适合您的需求。

但是,我想提一下,我会做什么!相反,我赞成的方法是使用适当的名称。例如,

inplace_lowercase(str);

几乎说了它将要做什么。显然,inplace_lowercase()实际上是一种算法,并且可以合理地将其称为

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

以下是我不喜欢通过指针传递参数和/或为什么我不相信如何传递参数的明确指示的一些原因:

  • 指针可以为空。在我看来,强制参考参数应该是一个参考。
  • 通过指针传递仍然不表示参数是否可以被修改,因为参数可能不是T const*
  • 有了有意义的名字,实际上更容易理解首先发生的事情。
  • 在不查阅其文档的情况下调用某些内容和/或知道被调用函数将执行什么操作无论如何都无法正常工作,并指出事情是如何通过试图解决更深层次问题的症状。

答案 1 :(得分:9)

我不确定我是否完全理解你的要求,但也许这是你可以使用的东西:

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
}

答案 2 :(得分:3)

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

答案 3 :(得分:2)

这是C ++:缺少inout参数并不意味着语言不足,这意味着您需要实现其他语言作为库的语言功能。

创建两个template类和函数。

in_param<T>T const&的包装,whilie io_param<T>T&引用的包装。您可以通过调用辅助函数inio来构造它们。

在内部,它们的行为类似于引用(通过重载)。

在外面,来电者必须在参数上调用inio,并在呼叫站点上标记。

out比较棘手:在内部,只有作业是合法的。理想情况下,我们甚至不会构造它:emplace方法可能会有所帮助。

但是,调用者需要一些通道才能知道参数是否已构建。

我要做的是out_param只有operator=,并且它会分配。 out将某些内容包装到out_param中。如果你想要延迟构造,请在out param中使用optional,这样就可以了。也许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); }

或类似的内容。

您现在可以获得如下语法:

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

未能在呼叫站点呼叫inioout会导致编译时错误。另外,out_param使得很难意外地读取函数中out变量的状态,在调用站点生成一些非常好的文档。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

我认为这是无用的(通过语言[1])。唯一需要的问题是:“我的对象是语义修改的吗?”,所以:

  • 当你阅读原型时,你知道一个函数是否可以修改一个对象(非const引用)或者不是(复制或const引用)。
  • 如果你必须确保不修改对象,当你使用一个函数(即使没有阅读[2]原型)时,使用const_cast。

[1]静态分析仪可以用于其目的 [2]如果你错过了,编译器会警告你。

答案 6 :(得分:0)

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