我写了一个具有这种形式的函数:
Result f( const IParameter& p);
我的意图是,此签名可以清楚地表明该函数未获得参数p
的所有权。
问题在于Result
将保留对IParameter
的引用:
class Result
{
const IParameter& m_p;
public:
Result( const IParameter& p )
: m_p( p ){ }
};
但是随后发生的是有人调用了这样的函数:
const auto r = f(ConcreteParameter{});
不幸的是,临时文件可以绑定到const引用,这导致崩溃。
问题是:如何弄清楚该函数不应与临时函数一起调用,并且在发生这种情况时可能会出现很好的编译错误?在这种情况下,声明它没有所有权,因为传递给它的结果会传播到函数调用范围之外,这实际上是错误的吗?
答案 0 :(得分:8)
最简单的方法是使用右值引用参数重载该函数。那些优先于临时引用的const引用,因此将选择它们。如果然后删除所说的重载,则会得到一个不错的编译器错误。对于您的代码,如下所示:
Result f( const IParameter&& ) = delete;
您也可以使用Result
做同样的事情来修饰它,看起来像这样:
class Result
{
const IParameter& m_p;
public:
Result( const IParameter& p )
: m_p( p ){ }
Result( const IParameter&& ) = delete;
};
答案 1 :(得分:3)
您可以从重载集中手动删除接受IParameter&&
右值的构造函数:
class Result
{
// ...
public:
Result( IParameter&& ) = delete; // No temporaries!
Result( const IParameter& p );
};
当客户端代码尝试通过实例化对象时
Result f(ConcreteParameter{}); // Error
采用const
限定引用的构造函数由于缺少const
-ness而没有匹配,但是右值构造函数完全匹配。由于这个= delete
d,编译器拒绝接受这样的对象创建。
请注意,如评论中所指出的那样,可以使用const
限定的临时人员来规避,有关如何确保不会发生这种情况的信息,请参见@NathanOliver's answer。
还请注意,并非所有人都同意这是一种好习惯,例如,看看here(在15:20)。
答案 2 :(得分:3)
很难说。最好的方法是证明Result
的生存期不能超过构造它的IParameter
的生存期。
在某些情况下,临时有效的临时构造函数是有效的。考虑一下:
doSomethingWithResult(Result{SomeParameterType{}});
删除具有临时性的构造函数会阻止这样的有效代码。
此外,删除右值构造函数不会阻止所有情况。考虑一下:
auto make_result() -> Result {
SomeParameterType param;
return Result{param};
}
即使删除了带有临时函数的构造函数,无效代码仍然非常容易制作。无论如何,您 都必须记录参数的寿命要求。
因此,如果您仍然要记录此类行为,我将选择标准库对string views的作用:
int main() {
auto sv = std::string_view{std::string{"ub"}};
std::cout << "This is " << sv;
}
它不会阻止从临时字符串构造字符串视图,因为它很有用,就像我的第一个示例一样。
答案 3 :(得分:3)
通常,如果函数通过const&
接收值,则可以预期该函数将使用该值,但不会保留该值。您确实持有对值的引用,因此您可能应该更改参数类型以使用shared_ptr
(如果资源是必需的)或weak_ptr
(如果资源是可选的)。否则,您会不时遇到此类问题,因为没人会阅读文档。
答案 4 :(得分:0)
我已经将@NathanOliver答案投票为最佳答案,因为我真的认为它已提供了我提供的信息。另一方面,当功能比我最初的示例中的功能更复杂时,我想分享我认为是解决此特定情况的更好解决方案。
$Subnet1
解决方案的问题在于,它随着参数的数量呈指数增长,假设在函数调用结束后所有参数都需要保持活动状态,并且您需要进行编译时检查以确保您的用户API并未尝试将这些参数的所有权提供给函数:
delete
我们需要void f(const A& a, const B& b)
{
// function body here
}
void f(const A& a, B&& b) = delete;
void f(A&& a, const B& b) = delete;
void f(A&& a, B&& b) = delete;
所有可能的组合,从长远来看,这将很难维持。因此,我提出的解决方案是利用以下事实:通过移动包装delete
的{{1}}构造函数已在STD中删除,然后编写:
reference_wrapper
这样,所有无效的过载将被自动删除。到目前为止,我认为这种方法没有任何缺点。