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`
然后我发现 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。我还省略了指针有效性检查。
以这种方式实现 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_ptr
或 const&
的理由。它不使用参数的共享性,也不希望远程更改它的值。是会消耗的"sink"参数,移动对象成本低
WidgetProxy::setName
应该取一个 std::string
。廉价移动数据的接收器参数应该按值取值。在这里使用智能指针似乎是一个可怕的想法;为什么 shared_ptr
让你的生活变得复杂?
我遇到过这样一种情况,我需要通过 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`
然后我发现 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。我还省略了指针有效性检查。
以这种方式实现 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_ptr
或 const&
的理由。它不使用参数的共享性,也不希望远程更改它的值。是会消耗的"sink"参数,移动对象成本低
WidgetProxy::setName
应该取一个 std::string
。廉价移动数据的接收器参数应该按值取值。在这里使用智能指针似乎是一个可怕的想法;为什么 shared_ptr
让你的生活变得复杂?