Умные указатели, это и конструкторы - PullRequest
0 голосов
/ 17 января 2019

Synoposis: конвертирует необработанные указатели в первом блоке кода ниже в интеллектуальные указатели, оставляя остальную часть функциональности как без изменений.


Я писал код, в котором я пытаюсь передать указатель в конструктор объекта, чтобы сделать его указателем на созданный объект к тому времени, когда конструктор вернется.

(Примечание: это возникло в гораздо более сложной ситуации, но я упростил ее до сути проблемы. Таким образом, если вы попытаетесь спросить почему я делаю это, вы выиграли Не обязательно найти разумный ответ в этом контексте - но не в этом суть вопроса.)

После борьбы с реализацией интеллектуальных указателей я вернулся к необработанным указателям, чтобы убедиться, что не пропустил фундаментальную проблему:

#include <iostream>

class Thing {
 public: 
  Thing(int data, Thing* thing_ptr) {
    data_ = data;
    *thing_ptr = *this;
  }

  void PrintData() {
    std::cout << data_ << std::endl;
  }

 private:
  int data_;
};

int main() {
  Thing* thing_ptr;
  Thing t(6, thing_ptr);
  thing_ptr->PrintData();

  return 0;
}

Это прошло без проблем, но когда я вернулся к умным указателям, я не мог воспроизвести эту функцию. Основная проблема в том, что я не знаю, как инициализировать умный указатель, не выполняя ненужную дополнительную работу или не ведя себя правильно.

Первое, что я попробовал, было:

#include <iostream>
#include <memory>

class Thing {
 public: 
  Thing(int data, std::unique_ptr<Thing>& thing_ptr) {
    data_ = data;
    thing_ptr = std::unique_ptr<Thing>(this);
  }

  void PrintData() {
    std::cout << data_ << std::endl;
  }

 private:
  int data_;
};

int main() {
  std::unique_ptr<Thing> thing_ptr;
  Thing th(6, thing_ptr);
  thing_ptr->PrintData();

  return 0;
}

, что, как я полагаю, завершится неудачей (дамп ядра во время выполнения), потому что this на самом деле не указатель на Thing, а скорее указатель на неинициализированный блок памяти с подходящим размером для хранения Thing. На самом деле я не уверен на 100%, что здесь происходит, но, поскольку это не удалось как с unique_ptr, так и с shared_ptr, я решил сначала инициализировать смарт-указатель, а затем присвоить this его содержание.

Проблема в том, что я не могу обойтись без создания дополнительного объекта с помощью этого метода (который в свою очередь требует даже добавления дополнительного конструктора).

  Thing() : data_(0) {}

  Thing(int data, std::unique_ptr<Thing>& thing_ptr) {
    data_ = data;
    if (!thing_ptr) {
      thing_ptr = std::make_unique<Thing>();
    }
    *thing_ptr = *this;
  }

Здесь я делаю Thing::Thing() только для того, чтобы выделить его на следующей строке, что, конечно, не является правильным подходом.

Может ли кто-нибудь умный указать мне правильное направление?

1 Ответ

0 голосов
/ 17 января 2019

Наиболее распространенное использование интеллектуальных указателей - это управление временем жизни объекта. Но обратите внимание, что когда вы объявляете переменную с типом класса, как в

Thing th(6, thing_ptr);

у вас нет выбора в отношении времени жизни этого объекта: оно заканчивается в следующем заключении }, если оно объявлено в функциональном блоке, или в конце времени жизни содержащего объекта, если оно объявлено в качестве члена класса, или в конце программы, если объявлен как член пространства имен. std::unique_ptr<T> с использованием средства удаления по умолчанию может содержать только указатель на объект, созданный с использованием new, поскольку средство удаления по умолчанию пытается использовать delete.

Если ваши указатели всегда будут на объекты с обычными объявлениями, подобными этим, и не созданы с использованием new, то вы застряли, убедившись, что указатели используются только в течение времени жизни объектов, на которые они указывали в. И в этом случае не так много преимуществ от использования std::unique_ptr; вы можете пойти дальше и использовать необработанный указатель, который по-прежнему является обычным способом представления указателя, когда что-то другое (в данном случае сам язык C ++) отвечает за жизненные проблемы.

Если вам нужна возможность указателей, которые переживают функциональный блок, в котором создается объект, вы не можете использовать обычный синтаксис объявления объекта. Одна из возможных альтернатив - потребовать, чтобы все объекты создавали функцию create вместо прямого объявления объекта:

class Thing {
public:
    static std::unique_ptr<Thing> create(const Thing& src)
    { return { new Thing(src) }; }
    static std::unique_ptr<Thing> create(int data)
    { return { new Thing(data) }; }

    void PrintData() const;

private:
    // All constructors private, to make sure a Thing can ONLY be
    // created by a create() function - including the copy constructor.
    Thing(const Thing&) = default;
    explicit Thing(int data) : data_(data) {}

    int data_;
};

int main() {
    auto thing_ptr = Thing::create(6);
    thing_ptr->PrintData();
}
...