移动语义与返回shared_ptr?

Move semantic vs returning a shared_ptr?

本文关键字:ptr shared 返回 语义 移动      更新时间:2023-10-16

我知道C++程序员被鼓励使用值语义。但是在我的工作中,我注意到一种模式,一些程序员使用引用语义,准确地说,他们使用shared_ptr我会使用值语义。

例如,为了给它一个上下文,我有一个读取数据库页面并返回其内容的 API。我看到有两种方法可以做到这一点。

选择 1 值语义:

DBPage readDatabasePage(int number) { // number is the for which page to read
DBPage page;
... // reading the database page
return page; // here we have RVO/move semantic to help us so it is not inefficient
} 

选择 2 引用语义:

std::shared_ptr<DBPage> readDatabasePage(int number) { // ditto
std::shared_ptr<DBPage> page = std::make_shared<DBPage>();
...
return page;
}

第二种选择对我来说似乎没问题,因为我看不出这样做有什么缺点。所以我想理解的是,为什么我们鼓励人们使用价值语义。这里的选择 2 有什么问题?

默认情况下,首选值语义,因为它不分配内存 => 没有内存泄漏,没有损坏的内存等。

每个指针类型都有自己的语义。 仅当shared_ptr控制的资源将被共享时才应使用,因此重要的是它将一直存在到最后一个引用(指针(。在您的示例中,shared_ptr是不合适的。如果需要在您的示例中使用指针(例如DBPage太大而无法存储在堆栈上(,它应该是unique_ptr的。

您的示例可能不完整,稍后结果确实会"共享"。我会说即使在这种情况下也应该使用unique_ptr,然后"转换"为shared_ptr,否则功能签名具有误导性,有时它是用户唯一可见的东西。虽然在这种情况下这是有争议的。

此外,shared_ptrunique_ptr慢,也比std::move慢得多,因为它使用原子操作进行引用计数。与简单的int.operator++相比,它们非常昂贵,尽管它仅在性能关键的情况下是一个问题。

这取决于 DBPage 的复制成本。 例如,如果 DBPage 是一个包含一些指向数据的指针的类,则复制起来可能很便宜,并且将其存储在shared_ptr中可能会增加不必要的开销。

另一方面,也许DBPage复制起来很昂贵。 您提到了 RVO 和移动语义,这些在从函数返回 DBPage 时很好,但是如果用户实际上希望保留引用相同数据的两个变量,并且他们希望数据的生存期是两个变量生存期中的最大值,那么shared_ptr是自然而然的。

如果用户最终需要shared_ptr<DBPage>但您给他们DBPage,他们可能需要复制数据才能获得他们想要的东西。

简而言之,您需要了解您的用户和实际数据。

在两种不同的情况下,您希望创建对象。您的"值语义"在您希望某些内存成为返回对象(堆栈内存、数组中的内存等(的情况下运行良好。当您希望对象生存期超出其范围(并存在于堆中(时,您的"引用语义"非常有用。

为了专门解决您的问题,当您想将结果存储在std::vector<DBPage>中时,选择 2 将不容易使用。即使使用移动语义,为作业提供正确的功能也是更好的选择。