正在修改const shared_ptr&传递的数据。好?

时间:2017-08-10 15:29:03

标签: c++ c++11 shared-ptr

我遇到过一种情况,我需要通过接受const 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`

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

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>&函数参数时通常会发生这种行为吗?
  3. 更新:发布的代码段大大简化了。在库中有一个不同的类型代替std :: string。我也省略了指针有效性的检查。

3 个答案:

答案 0 :(得分:3)

  

像这样实现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:操作的最终结果删除了指针对象所持有的字符串。这会影响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&amp;时,通常会期望这样的行为。功能参数?

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

答案 1 :(得分:2)

你误解了这意味着什么:

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使你的生活变得复杂?

答案 2 :(得分:0)

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

以这种方式实施WidgetProxy::setName只是一个错误,因为您无法真实地期望shared_ptr管理的对象可以移动。