Есть ли компромисс между управлением памятью и простым / идиоматическим 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, но я понял, что такой подход заставит память продолжать расти, даже когда больше нет ссылок на некоторые указатели (и умные указатели уже обрабатывают эту часть).
На данный момент я почти смирился с принятием умных указателей (хотя везде могут быть операторы с бриллиантами и двоеточиями), чтобы избежать утечек памяти .