【问题标题】:Working around the C++ limitation on non-const references to temporaries解决 C++ 对临时对象的非常量引用的限制
【发布时间】:2010-09-16 21:48:18
【问题描述】:

我有一个 C++ 数据结构,它是其他计算所需的“便签本”。它的寿命不长,并且不经常使用,因此对性能不是很重要。但是,它在其他可更新跟踪字段中包含一个随机数生成器,虽然生成器的实际值并不重要,但更新值而不是复制和重用该值很重要。 这意味着一般来说,此类的对象是通过引用传递的。

如果一个实例只需要一次,最自然的方法是在需要的地方构造它们(可能使用工厂方法或构造函数),然后将暂存器传递给使用方法。消费者的方法签名使用按引用传递,因为他们不知道这是唯一的用途,但工厂方法和构造函数按值返回 - 您不能通过引用传递未命名的临时对象。

有没有办法避免用讨厌的临时变量阻塞代码?我想避免以下情况:

scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
xyz.initialize_computation(useless_temp);

我可以在本质上制作暂存器 mutable 并只标记所有参数 const &amp;,但这并没有让我觉得这是最佳实践,因为它具有误导性,而且我不能为我不完全的课程这样做控制。通过右值引用传递将需要向所有暂存器使用者添加重载,这违背了目的 - 具有清晰简洁的代码。

考虑到性能并不重要(但代码大小和可读性是关键),传递此类暂存器的最佳实践方法是什么? 如果 必需的,但最好只有 C++03 的特性就足够了。

编辑:需要明确的是,使用临时是可行的,不幸的是我想避免代码中的混乱。如果你从不给​​临时文件起名字,它显然只使用过一次,而且要阅读的代码行数越少越好。此外,在构造函数的初始化器中,不可能声明临时对象。

【问题讨论】:

  • 您可以按值而不是参考来获取“便签本”吗?这样它可能是临时的和可变的。
  • 这样做意味着制作一个副本——在上述临时变量无用的情况下很好,但当我确实需要重用该变量时就不那么好了;这意味着以相同的状态重新运行 rng,这是个坏主意。
  • @Eamon Nerbonne:可能,只是概念上。在许多编译器的优化构建中,参数将“就地”构建。
  • @Charles:我认为他的意思是有时他会使用按顺序传递给几个例程的实际对象。在这种情况下,他不想为每个例程制作副本,它们必须通过引用传递。
  • 我的意思是,在某些地方useless_temp 实际上被重复使用了多次,因此并非没用。在 that 场景中按值传递意味着多次重复使用 same 值,从而破坏暂存器。想想按值传递一个随机数生成器 - 如果你这样做两次,两个实例将产生完全相同的随机数序列,这是不可取的。

标签: c++ reference c++11 constants temporaries


【解决方案1】:

虽然不能将右值传递给接受非常量引用的函数,但可以在右值上调用成员函数,但成员函数不知道它是如何被调用的。如果返回对当前对象的引用,则可以将右值转换为左值:

class scratchpad_t
{
    // ...

public:

    scratchpad_t& self()
    {
        return *this;
    }
};

void foo(scratchpad_t& r)
{
}

int main()
{
    foo(scratchpad_t().self());
}

请注意对self() 的调用如何产生一个左值表达式,即使scratchpad_t 是一个右值。

如果我错了,请纠正我,但右值引用参数不接受左值引用,因此使用它们需要向所有暂存器使用者添加重载,这也是不幸的。

嗯,你可以使用模板...

template <typename Scratch> void foo(Scratch&& scratchpad)
{
    // ...
}

如果您使用右值参数调用foo,则Scratch 将推导出为scratchpad_t,因此Scratch&amp;&amp; 将是scratchpad_t&amp;&amp;

如果你用左值参数调用fooScratch 将被推导出为scratchpad_t&amp;,并且由于引用折叠规则,Scratch&amp;&amp; 也将是scratchpad_t&amp;

请注意,形式参数scratchpad 是一个名称,因此是一个左值,无论它的类型是左值引用还是右值引用。如果您想将scratchpad 传递给其他函数,则不再需要这些函数的模板技巧,只需使用左值引用参数即可。

顺便说一句,你知道xyz.initialize_computation(scratchpad_t(1, 2, 3)); 中涉及的临时暂存器将在initialize_computation 完成后立即销毁,对吗?将引用存储在 xyz 对象中以供以后的用户使用将是一个非常糟糕的主意。

self()不必是成员方法,可以是模板函数

是的,这也是可能的,虽然我会重命名它以使意图更清晰:

template <typename T>
T& as_lvalue(T&& x)
{
    return x;
}

【讨论】:

  • 我意识到它会被立即销毁。暂存器只有一堆初始化所需的值和存储空间。模板不是一个有吸引力的选择,因为它们需要相当大的扩展头文件大小 - 这样做的目的是提高可读性。另一方面,self() 函数听起来很有趣……
  • 这解决了我的问题:self() 不需要是成员方法,它可以是模板函数ala std::move,当然!
【解决方案2】:

问题就在于:

scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);

丑吗?如果是这样,那为什么不改成这样呢?:

auto useless_temp = factory(rng_parm);

【讨论】:

  • @PigBen:因为auto 还没有在标准中,我们中的一些人没有能够冒险在标准之外的奢侈。
  • 嗯,这是一个原因,但我也想避免无意义的代码行,在构造函数的初始化列表中,我真的不能声明一个临时的.
  • @Billy:他说“如果需要,可以使用 C++0x 功能。”如果没有其他好的解决方案,那么它是必需的。
  • @PigBen:这就是我没有投反对票的原因。但是,我确实回答了您提出的问题,即“为什么不直接改成这个?” :)
  • 无论如何,感谢auto 的提醒 - 我期待着转储 C++03 支持 :-)。
【解决方案3】:

就个人而言,我宁愿看到const_cast 而不是mutable。当我看到mutable 时,我假设有人在做合乎逻辑的const-ness,不要想太多。 const_cast 但是会引发危险信号,因为这样的代码应该如此。

一种选择是使用 shared_ptr 之类的东西(auto_ptr 也可以工作,具体取决于 factory 正在做什么)并按值传递它,这样可以避免复制成本并只维护一个实例,但可以从你的工厂方法传入。

【讨论】:

  • shared_ptr 听起来很合理,尽管这意味着在整个 API 中使用它,这可能比避免临时使用更麻烦。 const_cast 有什么帮助?
  • 在临时工上使用const_cast 的官方说法是什么?我的直觉说那是UB,但我不确定。实际上,我无法想象它不适用于复杂类型,但我对法律后果感到好奇。
  • @Dennis:你不会用在临时的,你用在临时的成员身上。
  • shared_ptr、auto_ptr +1,你也应该指出 unique_ptr。
  • @Stephane:我从不推荐使用在当前标准下无法实现的库。 Unique_ptr 很好,但在移动语义成为标准之前,我不太愿意推荐使用它。
【解决方案4】:

如果您在堆中分配对象,您可能可以将代码转换为:

std::auto_ptr<scratch_t> create_scratch();

foo( *create_scratch() );

工厂创建并返回auto_ptr,而不是堆栈中的对象。返回的auto_ptr 临时对象将拥有该对象的所有权,但您可以在临时对象上调用非常量方法,并且可以取消引用指针以获得真正的引用。在下一个序列点,智能指针将被销毁并释放内存。如果您需要将相同的scratch_t 连续传递给不同的函数,您只需捕获智能指针即可:

std::auto_ptr<scratch_t> s( create_scratch() );
foo( *s );
bar( *s );

这可以在即将发布的标准中替换为std::unique_ptr

【讨论】:

  • 工厂方法可能是构造函数,但除此之外,这听起来几乎随时都可以工作;本质上,如果实现略有不同,它与 FredOverflow 的建议类似:您使用方法(此处为运算符重载)返回引用以绕过对右值引用的通常限制。事实上,你可以用一个普通的包装结构和一个隐式转换运算符替换 auto_ptr - 对吧?
  • 是的,它们在实现方面非常相似。问题是,通过提供一个返回引用或强制转换运算符的方法,您正在玩​​一种违反限制的非惯用技巧,而这种方法是众所周知的惯用方法——以对象的动态分配为代价。这试图遵循最小意外的原则
【解决方案5】:

我将 FredOverflow 的回复标记为他建议使用方法来简单地返回非常量引用的答案;这适用于 C++03。该解决方案需要每个类暂存器类型的成员方法,但在 C++0x 中,我们也可以更一般地为任何类型编写该方法:

template <typename T> T & temp(T && temporary_value) {return temporary_value;}

此函数只是转发普通的左值引用,并将右值引用转换为左值引用。当然,这样做会返回一个可修改的值,其结果会被忽略 - 这恰好正是我想要的,但在某些情况下可能看起来很奇怪。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-29
    • 1970-01-01
    • 2019-02-05
    • 2012-08-10
    • 1970-01-01
    相关资源
    最近更新 更多