修改数据是否由 const shared_ptr&Ok 传递?

Is modifying data passed by const shared_ptr& Ok?

本文关键字:ptr Ok 传递 shared 数据 是否 const 修改      更新时间:2023-10-16

我遇到过一种情况,我需要通过接受常量shared_ptr引用的 setter 将属性设置为库对象。下面是一个简单的示例:

// library class
class WidgetProxy {
public:
void setName(const std::shared_ptr<std::string>& name);
// more methods
};

什么都没有怀疑,我这样使用它:

WidgetProxy widgetProxy(...);
auto name = std::make_shared<std::string>("Turing");
widgetProxy.setName(name);
// continue using `name`

然后我发现namesetName()电话后变得空了。幸运的是,库源代码可用,我能够检查实现。大致如下:

class WidgetImpl {
public:
void setName(std::string name)
{
name_ = std::move(name);
}
private:
std::string name_;
};
void WidgetProxy::setName(const std::shared_ptr<std::string>& name)
{
widgetImpl_.setName(std::move(*name));
}

因此setName()移出由shared_ptr包装的字符串,这在形式上是没有禁止的shared_ptr因为模板参数是std::string而不是const std::string

我的问题:

  1. 实现这样的WidgetProxy::setName()是正常的设计吗?
  2. 库用户在看到const shared_ptr<T>&函数参数时通常是否应该期望这种行为?

更新:发布的代码片段大大简化。在库中,有一个不同的类型代替了 std::string。我还省略了对指针有效性的检查。

像这样实现 setName(( 是一个正常的设计吗?

此实现样式是可以的:

void setName(std::string name)
{
name_ = std::move(name);
}

字符串首先由函数调用复制,复制的字符串移动到类成员。 生成的代码与传递对字符串的引用,然后复制到数据成员一样有效。

这个不是。而且我不推荐它。

void WidgetProxy::setName(const std::shared_ptr<std::string>& name)
{
widgetImpl_.setName(std::move(*name));
}

原因有二。 1:如果不保留指针,为什么需要 std::shared_ptr? 2:操作的最终结果删除了 pointee 持有的字符串。 这会影响shared_ptr的所有其他持有者,其中一些可能需要原始字符串的值。

编写此函数和关联的函数调用的更正确方法:

void WidgetProxy::setName(std::string name)
{
widgetImpl_.setName(std::move(name));
}
// call as:
if (strPtr)
proxy.setName(*strPtr);   // with strPtr being a std::shared_ptr<std::string>

库用户在看到 const shared_ptr 和函数参数时通常应该期待这种行为吗?

不。 这是一种编写库代码的糟糕方式。 如果调用方出于任何原因希望保留字符串,则必须使用原始字符串的副本创建一个shared_ptr。 另外,库代码甚至不会检查shared_ptr是否包含有效的指针! 非常非常调皮。

你误解了这意味着什么:

class WidgetProxy {
public:
void setName(const std::shared_ptr<std::string>& name);
};

setName引用一个可能可变的共享指针,它无权修改该指针。 此共享指针引用可变字符串。

这意味着在setName中,每当控制从编译器可见的内容中流出时,name的指针和有效性可能会更改(并且,您应该检查它是否没有(。

此可能可变的共享指针的不可变视图所指向的值是完全可变的。 您具有修改它的完全权限。

一些替代方案:

class WidgetProxy {
public:
void setName(std::shared_ptr<std::string> name);
};

这是指向可变字符串的本地共享指针。 它只能在本地修改,除非你泄漏了对它的引用。 引用的数据由任何其他代码操作,并且必须假定在保留本地上下文时被修改。 但是,除非您亲自清除它,否则它将在setName函数的生存期内保持有效指针。

class WidgetProxy {
public:
void setName(std::shared_ptr<std::string const> name);
};

这是指向您没有突变权限的字符串的本地共享指针。 如果它实际上在您离开本地代码的任何时候都是可变的,那么具有共享指向它的其他人可以修改它,并且应该假定它正在这样做。

class WidgetProxy {
public:
void setName(std::string name);
};

这是字符缓冲区的本地副本,其他人无法在函数中修改该缓冲区,并且您拥有该缓冲区。

class WidgetProxy {
public:
void setName(std::string const& name);
};

这是对可能可变的外部std::string的引用,每次在函数中保留本地代码时都必须假定更改该外部。


就个人而言,我认为WidgetProxy没有理由通过shared_ptrconst&进行论点. 它不使用参数的共享性,也不希望在其上远程更改值。 这是一个它将消耗的"接收器"参数,并且移动对象的成本很低。

WidgetProxy::setName应该采取std::string. 移动成本低的数据的下沉论点应按值计算。 在这里使用智能指针似乎是一个可怕的想法;为什么要用shared_ptr使你的生活复杂化?

WidgetImpl::setName()

以这种方式实现是完全可以的,因为它是从本地参数移动的。

以这种方式实现WidgetProxy::setName只是一个错误,因为您实际上不能期望shared_ptr管理的对象是可移动的。