如果您要查看此代码,
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
但是这个解决方案还有很多其他问题,我不想失去参考的好处。另外,我们仍在对行为做出假设。
答案 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 ++:缺少in
和out
参数并不意味着语言不足,这意味着您需要实现其他语言作为库的语言功能。
创建两个template
类和函数。
in_param<T>
是T const&
的包装,whilie io_param<T>
是T&
引用的包装。您可以通过调用辅助函数in
和io
来构造它们。
在内部,它们的行为类似于引用(通过重载)。
在外面,来电者必须在参数上调用in
或io
,并在呼叫站点上标记。
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) );
}
未能在呼叫站点呼叫in
,io
或out
会导致编译时错误。另外,out_param
使得很难意外地读取函数中out
变量的状态,在调用站点生成一些非常好的文档。
答案 4 :(得分:0)
如果你使用MS VC ++那么它可能是关于源代码注释语言(SAL)的有用信息 http://msdn.microsoft.com/ru-ru/library/hh916383.aspx
答案 5 :(得分:0)
我认为这是无用的(通过语言[1])。唯一需要的问题是:“我的对象是语义修改的吗?”,所以:
[1]静态分析仪可以用于其目的 [2]如果你错过了,编译器会警告你。
答案 6 :(得分:0)
这是传递引用的整点 - 在语法上不需要做任何与传递值不同的事情。