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

C++ 返回引用,但防止通过包装器或其他方式存储引用,c++,architecture,c++17,C++,Architecture,C++17,在我们的代码中,我们有时返回对对象的非常量引用以链接方法调用。例如: registry.GetComponent(entityID).GetParams().GetTuners().SetWidth(50) 有时,在我们的评论中,我们会收到存储引用的代码,并对其执行一些我们禁止的操作 auto& comp = registry.GetComponent(2); // ... do something with component // ... with our pooling, co

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

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值
执行这些方法

#包括
结构组件{
组件&&incValue()&&{return std::move(*this);}
组件&&decValue()&&{return std::move(*this);}
const Component&&print()const&{return std::move(*this);}
//进行复制,如有必要,将复制者移动到私有位置以禁止复制。
};
结构注册表{
成分试验;
const Component&&getConstComponent(){return std::move(test);}
Component&&getComponent(){return std::move(test);}
};
int main()
{
登记处;
registry.getComponent().incValue().decValue().print();
registry.getConstComponent().print();
//错误,&无法绑定&&
//Component&comp=registry.getComponent();
//const&can绑定&&
常量组件&comp=registry.getComponent();
//错误,打印仅适用于常量&&
//comp.print();
//是的,这是有效的(并且是完全有效的代码),它不是傻瓜式的。
std::move(comp.print();
//请注意,这允许通过move ctor轻松窃取组件的数据。
组件{registry.getComponent()};
返回0;
}
我不建议这样做,我只是说这是可能的,也许可以修改它来帮助您。我认为,没有多少开发人员熟悉基于重载的
&&&
,这可能会让人困惑。此外,它滥用移动语义,不进行任何资源转移。此外,可以使用
std::move
或direct cast覆盖它,但至少应该给用户一个暂停

如果您以正常方式在某处使用
组件
,这可能不起作用。一个不太好的解决方法是将l值方法设置为私有,并使用
friend
。更明智的版本是仅将此逻辑应用于包装器。缺点是组件接口的重复

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

  • 请考虑将复制、移动或删除驱动器设置为私有以禁用低存储。因为每个副本都是在
    CompRef
    自身内部制作的

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

一种中间方法,但相当有侵入性,就是通过继承将
组件
划分为l值和r值(=仅链)方法。然后,
Registry
可以仅返回对组件的r值视图的r值引用。这将允许在普通和仅链上下文中使用该组件


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

“我们重载了()运算符”是否应该重载
->
?如果要封装数据,请不要返回非常量引用。任何解决方法都只会导致一个混乱,但会阻止它被存储——如果程序员有足够的勇气存储引用,那么如果该引用稍后无效,那就是程序员的错。听起来您正试图阻止所有错误。是否要阻止调用方创建对您的值的引用(即阻止类似
auto&ref=registry.GetComponent(entityID)
)的操作)?或者您想阻止调用复制值(即
auto value=registry.GetComponent(entityID)
)?@Samaursa:使用
->
的目的是将更钝的
()。
语法转换为更容易理解的语法:
UpdateComponent(entityID)->UpdateParams()->UpdateTuners()->SetWidth(50)
这在API级别清楚地表明,您正在处理一个类似指针的构造,因此您不应该保留它,因为它指向的内容不一定在这里拥有。有趣的方法。我们可能会使用类似的东西,泰。对于您的要点,#1将是危险的,因为存储在包装器中的ref可能会失效,并且包装器可能会被复制(特别是在lambdas中),#2个副本将非常昂贵,而且当我在修改副本后需要更新原始组件时,更是如此#3令人惊讶的是,MSVC++和Clang都没有警告过这一点@Samaursa他们不会花费太多,我不是建议复制组件本身,只是复制包装器,它只是
类W{Comp