0

I'm trying to solve Leetcode Problem 22 (which is just a pretty simple application of recursion), and I was trying to memoize the results in order to speed up computation. However, when I try to store a list of pointers to vectors I run into a std::bad_alloc error, whereas when I simply store a list of pointers the program runs fine. I'm relatively new to C++ and pointers and memory allocation, so this might be a really stupid question, but I've been looking at the code for a while and I can't seem to figure it out.

So, this is the code:

#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;
}

I'm not quite sure where, or why, but this throws a std::bad_alloc error. However, changing memo to vector<vector<string>> (and changing other various small parts of the code so that it makes sense), it works fine.

What in particular is causing the error here? There's no infinite loop, the recursion has a well defined base case, and I don't think I'm allocating too much memory. I don't see how the program could be failing to allocate memory.

user3002473
  • 4,835
  • 8
  • 35
  • 61
  • 4
    `memo[n] = &result;` is invalid since `result` is a local variable within that function, which means its life-time ends with the end of the function and the pointer becomes invalid. – Some programmer dude Nov 18 '18 at 17:51
  • @Someprogrammerdude Ah ok, I thought it might have something to do with that. The idea of storing the adress of a local variable is still really fuzzy to me. Is this fixable? Is it possible to store a pointer to the value of `result` in the vector `memo`? – user3002473 Nov 18 '18 at 17:53
  • @user3002473 Yes, just move the definition of `vector result` before your definition of `generateParenthesis_recursive()`. – Marc.2377 Nov 18 '18 at 17:54
  • 1
    Don't use pointers? First and foremost concentrate on writing good code that *works*. Then if performance isn't good enough (which often *is* good enough) then profile and measure to find the bottlenecks. Fix one and measure again to find the next. And since optimizations often tend to make code hard to read and understand (and therefore hard to maintain) write comments about what the optimized code is doing and why its doing it that way. – Some programmer dude Nov 18 '18 at 18:00
  • 1
    And return your vectors by const&, they are costly to copy! – Matthieu Brucher Nov 18 '18 at 18:08
  • 2
    @MatthieuBrucher ... and fairly cheap to move, or even free if NRVO kicks in. in this case they can't be returned by & because they are calculated results in a local variable. – Thomas Nov 18 '18 at 18:30
  • Hey @user3002473, thanks for accepting my answer, but as some fellow commenters have pointed out, it is in fact quite broken. I implemented a 'correct' version - or so I believe. Still, I have concerns about its performance and memory usage - do I have your blessing to post it to [Code Review SE](https://codereview.stackexchange.com)? I'll link to your question and identify you as original author of course. – Marc.2377 Oct 12 '19 at 02:34
  • 1
    @Marc.2377 Absolutely, go right ahead! Never one to get in the way of the pursuit of correct information! – user3002473 Oct 13 '19 at 02:36

2 Answers2

0

Problem is here:

memo[n] = &result;

Because you are referencing to local variable. You can change it to something like this:

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

But remember that you need to free these pointers manually.

Afshin
  • 8,839
  • 1
  • 18
  • 53
-1

As Some programmer dude said in the comments, the culprit is this line

memo[n] = &result;

because you assign a reference to a local variable (result) to memo, which goes out of scope at the end of the function.


(Edit): The solution below is wrong; don't use it.

This can be fixed by moving the declaration vector<string> result; outside of the function, like this:

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

Consider using smart pointers - if you really need pointers, that is. See: What is a smart pointer and when should I use one?

Marc.2377
  • 7,807
  • 7
  • 51
  • 95
  • 2
    The whole reason C++ programmers are strongly advised not to use collections of naked pointers is because it's never clear who owns the pointed-to objects. This is exactly that problem -- you added pointers to objects to a vector you returned and didn't realize that the objects pointed to were owned by the function. C++ has easy ways to keep ownership clear (such as using collections of values, using `unique_ptr`, and so on). Please use them. – David Schwartz Nov 18 '18 at 18:08
  • Your "fix" breaks a bunch of other stuff (the recursive calls will act on a `result` vector that doesn't start empty, and the changes they make will affect the outer `result` vector, and all entries in `memo` will point to the same instance). In short, it is no fix at all. – Ben Voigt Dec 24 '18 at 17:51