将这个包装在智能指针中的问题传递给 C++ 中的方法
Posted
技术标签:
【中文标题】将这个包装在智能指针中的问题传递给 C++ 中的方法【英文标题】:Problems passing this wrapped in a smart pointer to a method in C++ 【发布时间】:2020-02-27 21:55:40 【问题描述】:我有 2 个相互依赖的类(循环依赖)。一个只是对象Person
,另一个监听对象PersonListener
的变化。 Person
类定义如下。
class Person
private:
std::string name;
PersonListener listener;
public:
Person(std::string name, PersonListener listener) : namename, listenerlistener ;
void setName(std::string name)
this->name = name;
auto sPointer = std::make_shared<Person>(*this); // problem here? makes a copy?
listener.nameChanged(sPointer); // how do I pass this by reference?
PersonListener
定义如下。
class PersonListener
public:
PersonListener()
void nameChanged(std::shared_ptr<Person> person)
std::cout << "changed" << std::endl;
// some mutation to person would occur here
// the way I am passing this of the Person does not allow me to reflect mutations
;
问题在于将Person
实例的this
传递给PersonListener
。当PersonListener
改变传入的共享指针时,不会反映更改。
我目前的解决方法是使用原始指针将PersonListener.nameChanged
重载为void nameChanged(Person *person)
。这种方法很好,但这会带来尴尬,因为我几乎在任何地方都使用智能指针,然后在这里使用原始指针(请注意,在我的真实示例中,传入原始指针也会在代码的其他地方产生影响)。
【问题讨论】:
请提供minimal reproducible example 你希望这条线做什么?auto sPointer = std::make_shared<Person>(*this);
【参考方案1】:
auto sPointer = std::make_shared<Person>(*this); // problem here? makes a copy?
是的,确实如此。
很容易想到使用
listener.nameChanged(std::shared_ptr<Person>(this));
但是,this
变成了shared_ptr
的托管对象,这是不对的。
我看不出你不能使用的任何原因
class PersonListener
...
void nameChanged(Person& person) ...
;
并用
调用它listener.nameChanged(*this)
更新,以回应 OP 的评论
如果您必须使用shared_ptr
,您可以在构造shared_ptr
时使用noop 删除器。
std::shared_ptr<Person> sPointer(this, [](Person* ptr) );
// ^^ The deleter. It doesn't do anything.
listener.nameChanged(sPointer);
【讨论】:
这也很好,除了侦听器将调用其他期望shared_ptr
的方法。这导致我在shared_ptr
和raw pointer
或shared_ptr
和pass-by-reference
之间重载方法签名。看来我无法摆脱方法重载。
@Yksisarvinen,很好的收获。确实如此。让我想一个更好的建议。【参考方案2】:
auto sPointer = std::make_shared<Person>(*this); // problem here? makes a copy?
是的,std::make_shared
总是创建一个新对象。在这种情况下,它调用Person
的复制构造函数并创建一个新的,然后它可以管理。
很难在没有上下文的情况下提出建议。可能最好的解决方案是 R Sahu 在this answer 中提供的,但它可以在不修改PersonListener
接口的情况下完成。
一件事是使用常规的std::shared_ptr
构造函数,同时提供一个非删除器:
auto sPointer = std::shared_ptr<Person>(this, [](auto)); //custom deleter which does nothing to the object owned
这个解决方案可能会让人们感到困惑 - 为什么在它不管理任何东西时使用 shared_ptr
?另一方面,使用this
可能是这样做的线索。
如果您希望能够从类内部获得功能性shared_ptr
,您还可以考虑enable_shared_from_this
。
class Person: public std::enable_shared_from_this<Person>
private:
std::string name;
PersonListener listener;
public:
Person(std::string name, PersonListener listener) : namename, listenerlistener ;
void setName(std::string name)
this->name = name;
auto sPointer = shared_from_this(); //get a shared_ptr to this
listener.nameChanged(sPointer);
请注意,这种方法有很多缺点:
Person
类的对象总是必须创建为 std::shared_ptr
(所以你不能在任何地方创建例如 Person myPerson;
)
你不能在构造函数中调用shared_from_this()
,因为父shared_ptr
还不存在。
它很容易导致循环依赖和内存泄漏,许多工具(如valgrind
)无法检测到这些。如果PersonListener
将接收到的指针存储为其成员,则不会删除任何对象。
【讨论】:
在我给出的例子中,我应该重载ctor
(一个有PersonListener
,一个没有PersonListener
)。 Person
对象可能会或可能不会注入侦听器(请注意,我的母舰是 Java)。
@JaneWayne 这并没有改变我的答案(但已经很晚了,所以我可能会忽略一些东西)。对 Java 程序员来说可能很奇怪的事情:listener
是构造函数中提供的侦听器的副本,它不是多态的(如果您打算以这种方式注入一些派生类,它将不起作用 - listener
始终是 PersonListener
没有别的)以上是关于将这个包装在智能指针中的问题传递给 C++ 中的方法的主要内容,如果未能解决你的问题,请参考以下文章