有时我们喜欢通过引用获取一个大参数,并且如果可能的话也使参考const宣称它是一个输入参数。但是通过制作引用const,编译器然后允许自己转换数据,如果它是错误的类型。这意味着它不那么有效,但更令人担心的是我认为我指的是原始数据;也许我会接受它的地址,而不是意识到我实际上是取了一个临时的地址。
此代码中对bar
的调用失败。这是可取的,因为引用的类型不正确。对bar_const
的调用也是错误的类型,但它默默地编译。这对我来说是不受欢迎的。
#include<vector>
using namespace std;
int vi;
void foo(int &) { }
void bar(long &) { }
void bar_const(const long &) { }
int main() {
foo(vi);
// bar(vi); // compiler error, as expected/desired
bar_const(vi);
}
传递轻量级只读引用最安全的方法是什么?我很想创建一个类似于参考的新模板。
(显然,int
和long
是非常小的类型。但是我已经遇到了可以相互转换的更大的结构。我不希望这种情况在我发生时默默发生采用const引用。有时,将构造函数标记为显式有用,但这并不理想)
更新:我想象一个如下系统:想象一下有两个函数X byVal();
和X& byRef();
以及下面的代码块:
X x;
const_lvalue_ref<X> a = x; // I want this to compile
const_lvalue_ref<X> b = byVal(); // I want this to fail at compile time
const_lvalue_ref<X> c = byRef(); // I want this to compile
该示例基于局部变量,但我希望它也可以使用参数。我希望得到某种错误消息,如果我不小心将ref-to-temporary或ref-to-a-copy传递给我认为我会传递一些轻量级的内容,例如ref-to-lvalue。这只是一个“编码标准”的东西 - 如果我真的想允许将ref传递给临时的,那么我将使用简单的const X&
。 (我发现this piece on Boost's FOREACH非常有用。)
答案 0 :(得分:7)
好吧,如果你的“大参数”是一个类,首先要做的是确保你明确标记任何单个参数构造函数(除了复制构造函数):
class BigType
{
public:
explicit BigType(int);
};
这适用于具有可能使用单个参数调用的默认参数的构造函数。
然后它不会自动转换为,因为编译器没有用于进行转换的隐式构造函数。你可能没有任何构成该类型的全局转换运算符,但如果你这样做,那么
如果这对您不起作用,您可以使用一些模板魔法,例如:
template <typename T>
void func(const T &); // causes an undefined reference at link time.
template <>
void func(const BigType &v)
{
// use v.
}
答案 1 :(得分:3)
如果您可以使用C ++ 11(或其中的一部分),这很容易:
void f(BigObject const& bo){
// ...
}
void f(BigObject&&) = delete; // or just undefined
这将起作用,因为绑定到rvalue ref优先于绑定到临时对象的引用-const。
您还可以利用隐式转换序列中仅允许单个用户定义转换的事实:
struct BigObjWrapper{
BigObjWrapper(BigObject const& o)
: object(o) {}
BigObject const& object;
};
void f(BigObjWrapper wrap){
BigObject const& bo = wrap.object;
// ...
}
答案 2 :(得分:2)
这很容易解决:停止通过引用获取值。如果您想确保参数可寻址,请将其设为地址:
void bar_const(const long *) { }
这样,用户必须传递指针。并且您无法获得指向临时的指针(除非用户非常恶意)。
话虽这么说,我认为你对这件事的看法是......错误的。它归结为这一点。
也许我会接受它的地址,而不是意识到我实际上是在接受临时的地址。
考虑恰好是临时的const&
的地址实际上是好的。问题是你不能长期存储它。你也不能转让它的所有权。毕竟,您有一个 const
参考。
这就是问题的一部分。如果您选择const&
,您的界面会说:“我被允许使用此对象,但我不拥有它,也不能将所有权授予其他人。”由于您不拥有该对象,因此无法长期存储该对象。这就是const&
的含义。
取一个const*
可能会有问题。为什么?因为你不知道那个指针来自哪里。谁拥有这个指针? const&
有许多语法保护措施可以防止你做坏事(只要你不接受它的地址)。 const*
什么都没有;你可以将指针复制到心脏的内容。您的界面没有说明您是否被允许拥有该对象或将所有权转让给他人。
这种含糊不清是C ++ 11具有unique_ptr
和shared_ptr
等智能指针的原因。这些指针可以描述真正的内存所有权关系。
如果您的函数的值为unique_ptr
,那么您现在拥有该对象。如果需要shared_ptr
,那么您现在分享该对象的所有权。有确保所有权的语法保证(再次,除非你采取不愉快的步骤)。
如果你不使用C ++ 11,你应该使用Boost智能指针来实现类似的效果。
答案 3 :(得分:1)
你不能,即使你可以,它可能也没有多大帮助。 考虑:
void another(long const& l)
{
bar_const(l);
}
即使你可以以某种方式阻止绑定到临时输入
bar_const
,可以使用引用调用another
之类的函数
绑定到一个临时的,你最终会遇到同样的情况。
如果您不能接受临时,则需要使用对a的引用 非const或指针:
void bar_const(long const* l);
需要左值来初始化它。当然,像
这样的功能void another(long const& l)
{
bar_const(&l);
}
仍然会导致问题。但是如果你全球采用这个惯例
如果对象生存期必须延伸到调用结束之外,则使用指针,
然后希望another
的作者会考虑他为什么要这么做
地址,并避免它。
答案 4 :(得分:0)
我认为你的int
和long
的例子有点像规范的C ++,你永远不会通过const引用传递内置类型:你通过值或非传递它们const参考。
因此,我们假设您有一个大的用户定义类。在这种情况下,如果它正在为您创建临时值,那么这意味着您为该类创建了隐式转换。您所要做的就是将所有转换构造函数(可以使用单个参数调用的构造函数)标记为explicit
,编译器将阻止自动创建这些临时值。例如:
class Foo
{
explicit Foo(int bar) { }
};
答案 5 :(得分:0)
(在我提出的另一个问题上,感谢this great answer回答我自己的问题。感谢@hvd。)
简而言之,将函数参数标记为volatile
意味着它不能绑定到右值。 (任何人都可以为此标准引用吗?Temporaries可以绑定到const&
,但显然不会绑定到const volatile &
。这就是我对g ++ - 4.6.1的看法。(额外:见{{ 3}}对于我头上方式的一些血腥细节:-)))
void foo( const volatile Input & input, Output & output) {
}
foo(input, output); // compiles. good
foo(get_input_as_value(), output); // compile failure, as desired.
但是,您实际上希望参数为volatile
。所以我给const_cast volatile
写了一个小包装器。所以foo的签名就变成了这个:
void foo( const_lvalue<Input> input, Output & output) {
}
包装器是:
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; }
};
这可以仅从左值
创建有任何缺点吗?这可能意味着我不小心误用了一个真正不稳定的物体,但在我的生命中,我再也没有使用volatile
。我认为这对我来说是正确的解决方案。
我希望能够养成默认使用所有合适参数的习惯。