我遇到过一种情况,我需要通过接受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
,因此正式未被禁止。
我的问题:
WidgetProxy::setName()
是正常的设计吗?const shared_ptr<T>&
函数参数时通常会发生这种行为吗?更新:发布的代码段大大简化了。在库中有一个不同的类型代替std :: string。我也省略了指针有效性的检查。
答案 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_ptr
或const&
提出论据。它不使用参数的共享,也不希望在其上远程更改值。它是消耗的“接收器”参数,移动对象的成本很低。
WidgetProxy::setName
应该std::string
。廉价移动数据的接收参数应采用按值计算。在这里使用智能指针似乎是一个可怕的想法;为什么用shared_ptr
使你的生活变得复杂?
答案 2 :(得分:0)
以这种方式实现WidgetImpl::setName()
完全没问题,因为它是从本地参数移动的。
以这种方式实施WidgetProxy::setName
只是一个错误,因为您无法真实地期望shared_ptr
管理的对象可以移动。