C++函数参数的所有权
C++ ownership of a function parameter
我写了一个具有以下形式的函数:
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{});
不幸的是,临时可以绑定到常量引用,这导致了崩溃。
问题是:我怎样才能清楚地表明该函数不应该用临时函数调用,并且发生这种情况时可能会有一个很好的编译错误?在这种情况下,声明它没有获得所有权实际上是错误的,因为将其传递给将在函数调用范围之外传播的结果?
明确这一点的最简单方法是使用右值引用参数重载函数。 这些优先于临时的常量引用,因此将选择它们。 如果你随后删除上述重载,你会得到一个很好的编译器错误。 对于您的代码,如下所示:
Result f( const IParameter&& ) = delete;
您也可以对Result
执行相同的操作来保护它,如下所示:
class Result
{
const IParameter& m_p;
public:
Result( const IParameter& p )
: m_p( p ){ }
Result( const IParameter&& ) = delete;
};
通常,如果函数通过const&
接收值,则预计该函数将使用该值,但不会保存它。您确实持有对值的引用,因此您可能应该将参数类型更改为使用shared_ptr
(如果资源是必需的)或weak_ptr
(如果资源是可选的)。否则,你会不时遇到这种问题,因为没有人阅读文档。
>很难说。最好的方法是记录Result
的寿命不得超过用于建造它的IParameter
。
有一些有效的临时函数作为构造函数发送的情况是完全有效的。想一想:
doSomethingWithResult(Result{SomeParameterType{}});
删除临时构造函数将阻止此类有效代码。
此外,删除右值构造函数不会阻止所有情况。想一想:
auto make_result() -> Result {
SomeParameterType param;
return Result{param};
}
即使删除了带有临时的构造函数,无效代码仍然很容易制作。无论如何,您都必须记录参数的生存期要求。
因此,如果您无论如何都必须记录此类行为,我会选择标准库对字符串视图的作用:
int main() {
auto sv = std::string_view{std::string{"ub"}};
std::cout << "This is " << sv;
}
它不会阻止从临时字符串构造字符串视图,因为它可能很有用,就像我的第一个示例一样。
您可以从重载集中手动删除接受IParameter&&
右值的构造函数:
class Result
{
// ...
public:
Result( IParameter&& ) = delete; // No temporaries!
Result( const IParameter& p );
};
当客户端代码尝试通过以下方式实例化对象时
Result f(ConcreteParameter{}); // Error
由于缺少const
-ness,采用const
限定引用的构造函数不匹配,但右值构造函数完全匹配。由于这个是= delete
d,编译器拒绝接受这样的对象创建。
请注意,正如评论中指出的那样,这可以通过const
合格的临时人员来规避,请参阅@NathanOliver的答案,了解如何确保这种情况不会发生。
另请注意,并非每个人都同意这是好的做法,例如,请看这里(15:20)。
我已经投票@NathanOliver答案为最佳答案,因为我真的认为它给出了我提供的信息。另一方面,我想分享我认为更好的解决方案来解决这个非常具体的情况,当函数比我最初示例中的函数更复杂时。
delete
解决方案的问题在于,它随着参数数量的增加呈指数级增长,假设所有参数都需要在函数调用结束后保持活动状态,并且您希望在编译时检查 API 的用户是否未尝试将这些参数的所有权授予函数:
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
所有可能的组合,从长远来看,这将很难维持。所以我提出的解决方案是利用这样一个事实,即通过移动包装T
的reference_wrapper
构造函数已经在 STD 中删除,然后编写以下内容:
using AConstRef = reference_wrapper<const A>;
using BConstRef = reference_wrapper<const B>;
void f(AConstRef a, BConstRef b)
{
// function body here
}
这样,所有无效的重载将被自动删除。到目前为止,我没有看到这种方法有任何缺点。
- 如何反转整数参数包
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 如何使用默认参数等选择模板专业化
- 模板参数替换失败,并且未完成隐式转换
- 具有默认模板参数的多态类的模板推导失败
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 函数调用中参数的顺序重要吗
- 部分定义/别名模板模板参数
- 模板-模板参数推导:三个不同的编译器三种不同的行为
- 使用不带参数的函数访问结构元素
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 如何在OMNET++中指定与命令行参数组合的输出文件名
- 如何使用Luacneneneba API正确读取字符串和表参数
- 在派生函数中指定void*参数
- 视图中的参数推导失败:take_while
- static_assert在宏中,但也可以扩展到可以用作函数参数的东西
- C++函数参数的所有权
- MSXML方法是否对其BSTR参数拥有内存所有权?
- Qt信号:将动态分配参数的所有权传递给插槽