将这个包装在智能指针中的问题传递给 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&lt;Person&gt;(*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_ptrraw pointershared_ptrpass-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++ 中的方法的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中创建对象并将指针传递给 python

如何将函数指针传递给成员函数c++?

工厂:如何将临时智能指针传递给函数。 C++

使用 SWIG 将 C++ 对象指针传递给 Python,而不是再次返回 C++

将指针向量传递给c ++中的函数[重复]

通过 C++ 中的指针将子对象值传递给被调用者类