Предотвращение утечек памяти и простое / идиоматическое c программирование на C ++ - PullRequest
0 голосов
/ 22 февраля 2020

Есть ли компромисс между управлением памятью и простым / идиоматическим c программированием на C ++?

Например, допустим, у меня есть следующие классы. У нас есть Item, который кто-то может купить, и ShoppingList из этих Items. Скажем так: Item и ShoppingList - это большие структуры данных с большим количеством функций, чем показано, и мы не хотели бы передавать их по значению в обычном режиме (но в этом примере они урезаны до базовых функций).

class Item {
 private:
  std::string name;
  float cost;
 public:
  Item(std::string name, float cost) : name{name}, cost{cost} { }
  std::string toString(void) {
    return name + " = " + std::to_string(cost);
  }
  ~Item() { std::cout << "~Item()" << std::endl; }
};

class ShoppingList {
 private:
  std::vector<Item *> items;
 public:
  ShoppingList() {}
  void addItem(Item &item) {
    items.push_back(&item);
  }
  std::vector<Item *> getItems() {
    return items;
  }
  ~ShoppingList() {
    std::cout << "~ShoppingList()" << std::endl;
  }
};

Теперь я хочу, чтобы я мог conveniently сделать что-то вроде следующего (вставить аргумент).

ShoppingList shoppingList{};
shoppingList.add(Item{"soap", 1.99}); // no

C ++ 17 также не позволит мне попробовать это тоже.

ShoppingList shoppingList{};
shoppingList.add(&Item{"soap", 1.99}); // no
shoppingList.add(new Item{"soap", 1.99}); // no

Вместо этого я должен сделать что-то вроде этого.

ShoppingList shoppingList{};

Item soap{"soap", 1.99};
shoppingList.add(soap);

Выше «хорошо» для добавления 1 элемента, но все становится «хуже», когда я пытаюсь использовать для l oop. item, созданный в каждом l oop, заканчивается тем же адресом, и я не получаю ожидаемого эффекта.

ShoppingList shoppingList{};

auto products = std::vector<std::string>{"brush", "comb"};
for (auto product : products) {
  Item item{product, 0.99};
  shoppingList.addItem(item);
}

Мне кажется необоснованным делать следующее.

ShoppingList shoppingList{};

Item item1{"item1", 0.99};
Item item2{"item2", 0.99};
Item item3{"item3", 0.99};
// and so on

shoppingList.addItem(item1);
shoppingList.addItem(item2);
shoppingList.addItem(item3);
// and so on

Существует так много способов подумать о том, как упростить использование кода. Одним из способов будет использование сырых указателей. Так что я могу изменить сигнатуру метода ShoppingList.addItem(...) следующим образом.

void addItem(Item *item);

Тогда я могу сделать несколько менее сложное использование объектов следующим образом.

ShoppingList shoppingList{};
shoppingList.addItem(new Item{"item1", 0.99});
shoppingList.addItem(new Item{"item2", 0.99});
shoppingList.addItem(new Item{"item3", 0.99});

С этим новым Подпись метода, для циклов должна работать.

ShoppingList shoppingList{};

auto products = std::vector<std::string>{"brush", "comb"};
for (auto product : products) {
  shoppingList.addItem(new Item{product, 0.99});
}

Хорошо, поэтому этот подход облегчает использование / запись кода, но valgrind анализ говорит Leak_DefintelyLost, где я начинаю использовать new.

Другой подход - умные указатели. Итак, давайте обернем объекты в умные указатели; в частности shared_pointer. Вот новый ShoppingList с использованием интеллектуальных указателей.

class ShoppingList {
 private:
  std::vector<std::shared_ptr<Item>> items;
 public:
  ShoppingList() {}
  void addItem(std::shared_ptr<Item> item) {
    items.push_back(item);
  }
  std::vector<std::shared_ptr<Item>> getItems() {
    return items;
  }
  ~ShoppingList() {
    std::cout << "~ShoppingList()" << std::endl;
  }
};

Использование будет выглядеть следующим образом.

ShoppingList shoppingList{};
shoppingList.addItem(std::make_shared<Item>("soap", 32.0));
shoppingList.addItem(std::make_shared<Item>("shampoo", 32.0));

valgrind не обнаруживает единственную проблему утечки памяти.

Единственная проблема, с которой я сталкиваюсь при использовании shared_ptr, - это многословность и вложенные типы. Эта декларация выглядит плохо для меня. Это много : и < с >.

std::vector<std::shared_ptr<Item>> items;

В одной ситуации, когда у меня была карта карт больших структур данных, я подумал об использовании общих указателей и в итоге получил объявление следующим образом. Если другой кодер смотрит на то, что является намерением (или если я сам вернусь позже, чтобы устранить неполадки), что намерение здесь?

std::map<std::string, std::map<std::string, std::vector<std::shared_ptr<SomeClass>>>> dataBag;

Мне кажется, что : и < и > загрязняют код и используют все для устранения утечек памяти. Я не уверен, что хорошо или плохо или идиоматизм c для написания C ++ 17, хотя он и лаконичен, но также предотвращает утечки памяти, усиливаемые языковыми конструкциями.

Теперь я столкнулся с идеей RAII . Но если посмеяться над некоторыми классами с помощью этого подхода (только для необработанных указателей), valgrind по-прежнему показывает Leak_DefinitelyLost.

Было бы полезно любое общее руководство, которое я бы назвал идиоматическим c C ++, чувствительным / предупреждающим утечку памяти. Я собирался go пойти дальше по пути создания фабрик для хранения всех указателей, которые я буду создавать через мой API, но я понял, что такой подход заставит память продолжать расти, даже когда больше нет ссылок на некоторые указатели (и умные указатели уже обрабатывают эту часть).

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

Ответы [ 2 ]

1 голос
/ 22 февраля 2020

Долгое время go Я получаю c++ совет.

Никогда не используйте необработанные указатели.

Как только вы не являетесь библиотекой низкого уровня разработчик. Вам не нужны сырые указатели. Не нужно Период.

1 голос
/ 22 февраля 2020

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

Что касается передачи по ссылке: прямо сейчас вы определяете параметр как ссылку на lvalue. Это означает, что ссылка может относиться только к lvalue. Таким образом, требование создать именованный объект (lvalue) затем передать его.

Вы, очевидно, не хотите этого. В вашем случае у вас есть пара вариантов. Одним из них является ссылка на const. В отличие от (неконстантной) ссылки lvalue, она может привязываться к временному объекту.

Другим вариантом будет использование ссылки на rvalue. Ссылка на rvalue имеет то преимущество, что она может связываться с rvalue (например, временным объектом), а также то, что она «знает», что имеет дело с rvalue, поэтому она может «красть» содержимое этого объекта. Это особенно полезно, если объект в основном содержит указатель на фактические данные, и в этом случае вы можете просто скопировать указатель (и изменить существующий объект, чтобы он не разрушал данные при его уничтожении).

В большинстве случаев лучше начинать с самого простого кода, который мог бы работать. Создайте вектор объектов и согласитесь с тем, что они будут скопированы. Некоторое время спустя, если вы обнаружите, что у вас есть реальное узкое место при копировании этих объектов, поскольку этот вектор перераспределяется, достаточно скоро изменить ваш код - например, для хранения std::unique_ptr s вместо непосредственного хранения объектов. Но это не ваш первый выбор. Скорее всего, при профилировании кода вы обнаружите, что копирование этих объектов вовсе не является узким местом, поэтому работа по предотвращению их копирования была бы напрасной.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...