返回引用,但防止通过包装或其他方式存储引用

问题描述

在我们的代码中,有时我们返回对对象的非常量引用以链接方法调用。例如:

registry.GetComponent(entityID).GetParams().GetTuners().SetWidth(50)

有时候,在我们的评论中,我们会收到存储引用并对其进行某些操作的代码,这是我们禁止的。

auto& comp = registry.GetComponent(2);

// ... do something with component
// ... with our pooling,comp could invalid at any point

我们大多数时候都抓住了它,但有时它会通过并导致难以发现崩溃。我们尝试实现一个包装器(在内部存储私有引用),该包装器将返回而不是引用,并且该包装器不可复制。由于不能重载点运算符(在这种情况下这是完美的),因此我们将()运算符重载。

现在我们有了

// all GetXXXXX are now const ref
// registry.GetComponent(entityID).GetParams().GetTuners().SetWidth(50)
registry.UpdateComponent(entityID)().UpdateParams()().UpdateTuners()().SetWidth(50)

// this is still possible,but easier to catch in code reviews
auto comp = registry.UpdateComponent(entityID);
comp().UpdateParams()().UpdateTuners()().SetWidth(50);

丑陋,但在代码审查中更明显,而且如果在本地存储然后使用,在代码审查中也很容易发现。

是否可以返回对可修改对象的引用,但阻止其存储,或者可以使用其他模式来安全修改嵌套对象。

注意:上面的内容在某种程度上反映了我们的代码,但是它们是非常人为的示例。我希望这个问题的意图很清楚。

解决方法

完全公开,我认为这没有太多。

如果要确保引用只能用于链接,请返回&&并仅对r值this实现方法。

#include <utility>

struct Component{
    Component&& incValue() &&{return std::move(*this);}
    Component&& decValue() &&{return std::move(*this);}
    const Component&& print() const &&{return std::move(*this);} 
// Make copy,move ctors private to disallow copies if necessary.
};

struct Registry{
    Component test;
    const Component&& getConstComponent(){ return std::move(test); }
    Component&& getComponent(){ return std::move(test); }
};
int main()
{
    Registry registry;

    registry.getComponent().incValue().decValue().print();
    registry.getConstComponent().print();

    //ERROR,& cannot bind &&
    //Component& comp = registry.getComponent();
    //const & can bind &&
    const Component& comp = registry.getComponent();
    // ERROR,print only works with const &&
    // comp.print();
    // Yes,this works (and is perfectly valid code),it is not fool-proof. 
    std::move(comp).print();
    //BEWARE,this allows easy theft of the Component's data via move ctor.
    Component thief{registry.getComponent()};
    return 0;
}

我不建议您这样做,我只是说有可能,也许可以对其进行修改以帮助您。我会说没有多少开发人员熟悉基于&,&&的重载,它可能会造成混乱。此外,它通过不进行任何资源转移来滥用移动语义。另外,可以使用std::move覆盖它,也可以直接强制覆盖它,但是它至少应该使用户有一个暂停。

如果您以常规方式在某处使用Component,则此方法可能无效。一种不太好的解决方法是将l值方法设为私有并使用friend。更合理的版本是仅将此逻辑应用于包装器。缺点是Component的界面重复。

  • Registry::getComponent()按值返回CompRef包装器,该包装器仅具有r值方法,该方法也按值返回其自身的副本。这个包装器应该很小,所以希望return CompRef by value+optimization == returning Component&

  • 考虑制作副本,将ctor设为私有以禁用存储。由于每个副本都是在CompRef内部制作的。

  • 注意::返回自身的r值仅在链中起作用,将其存储起来将创建一个悬挂的参考,因为原始参考是临时的。这应该不是问题,因为您不应真正使用它,但是任何访问都将成为UB。

一个中间层方法(但颇具侵入性)将通过继承将Component划分为l值和r值(仅链)方法。然后,Registry可以仅将r值引用返回到组件的r值视图。这样一来,既可以在常规环境中使用组件,也可以仅在链上使用该组件。

同样,在放入任何代码库之前,这需要更多的考虑。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...