是否可以通过常量引用获取参数,同时禁止转换,以便不传递临时变量?

Is it possible to take a parameter by const reference, while banning conversions so that temporaries aren't passed instead?

本文关键字:变量 禁止 引用 常量 可以通过 获取 参数 是否 转换      更新时间:2023-10-16

有时我们喜欢通过引用来获取一个大参数,并且如果可能的话,也使引用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);
}

传递轻量级只读引用的最安全方法是什么?我很想创建一个新的类似引用的模板。

(显然,intlong是非常小的类型。但是我被发现了可以相互转换的较大结构。我不希望这在我进行常量引用时悄无声息地发生。有时,将构造函数标记为显式帮助,但这并不理想(

更新:我想象一个像下面的系统:假设有两个函数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 传递给临时的,那么我将使用一个简单的const X&。(我发现Boost的FOREACH上的这篇文章非常有用。

好吧,如果你的"大参数"是一个类,首先要做的是确保你标记任何单个参数构造函数显式(除了复制构造函数(:

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

如果您可以使用 C++11(或其部分(,这很容易:

void f(BigObject const& bo){
  // ...
}
void f(BigObject&&) = delete; // or just undefined

Ideone上的活生生的例子。

这将起作用,因为绑定到右值引用比绑定到临时对象的 const引用更可取。

您还可以利用隐式转换序列中只允许单个用户定义转换的事实:

struct BigObjWrapper{
  BigObjWrapper(BigObject const& o)
    : object(o) {}
  BigObject const& object;
};
void f(BigObjWrapper wrap){
  BigObject const& bo = wrap.object;
  // ...
}

Ideone上的活生生的例子。

这很容易

解决:停止通过引用获取值。如果要确保参数可寻址,请将其设置为地址

void bar_const(const long *) { }

这样,用户必须传递指针。而且您无法获得指向临时的指针(除非用户非常恶意(。

话虽如此,我认为你对这件事的看法是......误头了。归根结底就是这一点。

也许我会接受它的地址,却没有意识到我实际上是在接受临时地址。

获取恰好是临时const&的地址实际上很好。问题是你不能长期存储它。您也不能转让它的所有权。毕竟,你得到了一个const参考。

这是问题的一部分。如果你拿一个const&,你的界面是说,"我被允许使用这个对象,但我不拥有它,也不能将所有权交给别人。由于您不拥有该对象,因此无法长期存储该对象。这就是const&的意思。

取而代之的是const*可能会有问题。为什么?因为你不知道那个指针是从哪里来的。谁拥有此指针? const&有许多语法保护措施来防止你做坏事(只要你不拿它的地址(。 const*一无所有;您可以将该指针复制到您的心内容。您的界面没有说明您是否被允许拥有该对象或将所有权转让给其他人。

这种歧义就是为什么 C++11 具有像 unique_ptrshared_ptr 这样的智能指针。这些指针可以描述真正的内存所有权关系。

如果您的函数按值获取unique_ptr,那么您现在拥有该对象。如果需要shared_ptr,那么您现在共享该对象的所有权。有语法保证可以确保所有权(同样,除非你采取不愉快的步骤(。

如果您不使用 C++11,则应使用 Boost 智能指针来实现类似的效果。

你不能

,即使你可以,也可能帮不上什么忙。考虑:

void another(long const& l)
{
    bar_const(l);
}

即使你可以以某种方式阻止绑定到临时作为输入 bar_const,像another这样的函数可以用引用调用绑定到一个临时的,你最终会陷入同样的境地。

如果您不能接受临时的,则需要使用对非恒量或指针:

void bar_const(long const* l);

需要一个左值来初始化它。 当然,像这样的函数

void another(long const& l)
{
    bar_const(&l);
}

仍然会引起问题。 但是,如果您在全球范围内采用该公约如果对象生存期必须超出调用结束,请使用指针,那么希望another的作者会思考他为什么要服用地址,并避免它。

我认为你用 intlong 的例子有点红鲱鱼,因为在规范C++你永远不会通过常量引用传递内置类型:你通过值或非常量引用传递它们。

因此,让我们假设您有一个大型的用户定义类。在这种情况下,如果它正在为您创建临时变量,则意味着您为该类创建了隐式转换。您所要做的就是将所有转换构造函数(可以使用单个参数调用的构造函数(标记为explicit,编译器将阻止自动创建这些临时函数。例如:

class Foo
{
    explicit Foo(int bar) { }
};

(回答我自己的问题,感谢我提出的另一个问题的出色回答。谢谢@hvd。

简而言之,将函数参数标记为volatile意味着它不能绑定到右值。(任何人都可以为此确定一个标准报价吗?临时者可以绑定到const&,但显然不能const volatile &。这就是我在 g++-4.6.1 上得到的。(额外:请参阅此扩展评论流以获取一些超出我头的血腥细节:-(((

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。所以我认为这对我来说是正确的解决方案。

我希望养成默认情况下使用所有合适参数执行此操作的习惯。

关于 ideone 的演示