Проблемы с передачей этого, заключенного в умный указатель на метод в C ++ - PullRequest
0 голосов
/ 28 февраля 2020

У меня есть 2 класса, которые зависят друг от друга (циклическая c зависимость). Один - это просто объект Person, а другой слушает изменения объекта PersonListener. Класс Person определяется следующим образом.

class Person {
 private:
  std::string name;
  PersonListener listener;
 public:
  Person(std::string name, PersonListener listener) : name{name}, listener{listener} {};
  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
  }
};

Проблема заключается в передаче this из Person экземпляр PersonListener. Когда PersonListener изменяет переданный в совместно используемый указатель, изменения не отражаются.

Текущий обходной путь - перегрузить PersonListener.nameChanged на void nameChanged(Person *person), используя необработанный указатель. Этот подход хорош, но он вводит неловкость в том смысле, что я использую умные указатели почти везде, а затем здесь, необработанный (обратите внимание, что в моем реальном примере передача необработанного указателя также имеет значение в других местах кода).

Ответы [ 2 ]

1 голос
/ 28 февраля 2020
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)

Update, в ответ на комментарий ОП

Если вам необходимо использовать shared_ptr, вы можете использовать oop при создании shared_ptr.

std::shared_ptr<Person> sPointer(this, [](Person* ptr) {});
//                                     ^^ The deleter. It doesn't do anything.
listener.nameChanged(sPointer);
.
0 голосов
/ 28 февраля 2020
auto sPointer = std::make_shared<Person>(*this); // problem here? makes a copy?

Да, std::make_shared всегда создает новый объект. В этом случае он вызывает конструктор копирования Person и создает новый, которым он может управлять.


Трудно предложить что-то без контекста. Вероятно, лучшее решение было предоставлено Р. Саху в этом ответе , но это можно было бы сделать без модификации интерфейса 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) : name{name}, listener{listener} {};
  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 сохранит указатель, полученный в качестве члена, ни один из объектов не будет удален.
...