const shared_ptr& 传过来修改数据好吗?

Is modifying data passed by const shared_ptr& Ok?

我遇到过这样一种情况,我需要通过 setter 接受 const shared_ptr 引用来将 属性 设置为库对象。这是一个简单的例子:

// 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。我还省略了指针有效性检查。

以这种方式实现 WidgetImpl::setName() 完全没问题,因为它是从本地参数移动的。

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

Is it a normal design to implement setName() like this?

这种实现方式还可以:

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

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

这个不是。我也不是不推荐。

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

有两个原因。 1:如果不保留指针,为什么需要 std::shared_ptr? 2:操作的最终结果删除了指针持有的字符串。这会影响 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>

Should a library user normally expect such behavior when they see a const shared_ptr& function parameter?

没有。这是一种糟糕的库编码方式。如果调用者出于任何原因希望保留该字符串,则他必须使用原始字符串的副本创建一个 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& 的理由。它不使用参数的共享性,也不希望远程更改它的值。是会消耗的"sink"参数,移动对象成本低

WidgetProxy::setName 应该取一个 std::string。廉价移动数据的接收器参数应该按值取值。在这里使用智能指针似乎是一个可怕的想法;为什么 shared_ptr 让你的生活变得复杂?