Что вызывает появление std :: bad_alloc в этом коде?

Я пытаюсь решить Leetcode Задача 22 (это довольно простое приложение рекурсии), и я пытался запомнить результаты, чтобы ускорить вычисления. Однако, когда я пытаюсь сохранить список указателей на векторы, я сталкиваюсь с ошибкой std::bad_alloc, тогда как когда я просто сохраняю список указателей, программа работает нормально. Я относительно новичок в C++, указателях и распределении памяти, поэтому это может быть действительно глупый вопрос, но я некоторое время смотрел на код и, похоже, не могу его понять.

Итак, это код:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        if (n == 0) {
            return {};
        }
        return generateParenthesis_recursive(n);
    }

    vector<vector<string>*> memo;
    vector<string> generateParenthesis_recursive(unsigned int n) {
        if (n < memo.size()) {
            return *(memo[n]);
        }

        vector<string> result;
        if (n == 0) {
            result = {""};
        }
        else if (n == 1) {
            result = {"()"};
        } 
        else if (n > 1) {
            vector<string> left, right;
            for (unsigned int k = 0; k < n; k++) {
                left  = generateParenthesis_recursive(k);
                right = generateParenthesis_recursive(n - k - 1);
                for (auto left_parenths : left) {
                    for (auto right_parenths : right) {
                        result.push_back("(" + left_parenths + ")" + right_parenths);
                    }
                }
            }
        }

        while (memo.size() <= n) {
            memo.push_back(nullptr);
        }
        memo[n] = &result;
        return result;
    }
};

int main(int argc, char const *argv[]) {
    Solution s;

    vector<string> output = s.generateParenthesis(4);
    for (auto s : output) {
        cout << s << " ";
    }
    cout << endl;

    return 0;
}

Я не совсем уверен, где и почему, но это вызывает ошибку std::bad_alloc. Однако, изменив memo на vector<vector<string>> (и изменив другие различные мелкие части кода, чтобы он имел смысл), он работает нормально.

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

memo[n] = &result; недействителен, поскольку result является локальной переменной в этой функции, что означает, что время ее существования заканчивается с окончанием функции, и указатель становится недействительным.
Some programmer dude 18.11.2018 18:51

@Someprogrammerdude А, ладно, я подумал, что это может иметь какое-то отношение к этому. Идея сохранения адреса локальной переменной все еще остается для меня туманной. Это поправимо? Можно ли сохранить указатель на значение result в векторе memo?

user3002473 18.11.2018 18:53

@ user3002473 Да, просто переместите определение vector<string> result перед своим определением generateParenthesis_recursive().

Marc.2377 18.11.2018 18:54

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

Some programmer dude 18.11.2018 19:00

И возвращайте свои векторы с помощью const &, их копирование дорого!

Matthieu Brucher 18.11.2018 19:08

@MatthieuBrucher ... и довольно дешевый в перемещении или даже бесплатный, если NRVO сработает. В этом случае они не могут быть возвращены с помощью &, потому что они вычисляются в локальной переменной.

Thomas 18.11.2018 19:30

Привет, @ user3002473, спасибо, что приняли мой ответ, но, как отметили некоторые другие комментаторы, он на самом деле совсем не работает. Я реализовал «правильную» версию - по крайней мере, мне так кажется. Тем не менее, у меня есть опасения по поводу его производительности и использования памяти - могу ли я опубликовать его в Code Review SE? Я дам ссылку на ваш вопрос и, конечно же, укажу вас как первоначального автора.

Marc.2377 12.10.2019 04:34

@ Marc.2377 Конечно, вперед! Никогда не мешайте поиску правильной информации!

user3002473 13.10.2019 04:36
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
604
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема здесь:

memo[n] = &result;

Потому что вы ссылаетесь на локальную переменную. Вы можете изменить его примерно так:

memo[n] = new vector<string>(result);

Но помните, что вам нужно освободить эти указатели вручную.

Ответ принят как подходящий

Как сказал Какой-то чувак-программист в комментариях, виновата эта строка

memo[n] = &result;

потому что вы назначаете ссылку на локальную переменную (result) на memo, которая выходит за рамки в конце функции.


(Редактировать): Решение ниже неверно; не используйте это.

Это можно исправить, переместив объявление vector<string> result;за пределами функции, например:

vector<vector<string>*> memo;
vector<string> result;
vector<string> generateParenthesis_recursive(unsigned int n) {
//...

Рассмотрите возможность использования умных указателей - если уж нужны указатели, то есть. См .: Что такое умный указатель и когда мне его использовать?

Вся причина, по которой программистам на C++ настоятельно не рекомендуется использовать коллекции голых указателей, заключается в том, что никогда не ясно, кому принадлежат указанные объекты. Это именно та проблема - вы добавили указатели на объекты в вектор, который вы вернули, и не осознали, что указанные объекты принадлежат функции. В C++ есть простые способы сохранить ясность владения (например, использовать коллекции значений, использовать unique_ptr и т. д.). Пожалуйста, используйте их.

David Schwartz 18.11.2018 19:08

Ваше «исправление» нарушает кучу других вещей (рекурсивные вызовы будут действовать на вектор result, который не начинается с пустого, и вносимые ими изменения повлияют на внешний вектор result, и все записи в memo будут указывать на один и тот же экземпляр ). Короче говоря, это вообще не исправление.

Ben Voigt 24.12.2018 18:51

Другие вопросы по теме