0

In the following snippet:

std::future<bool> result = std::async(std::launch::async, []()
{
    std::vector<char*> someLocalVariable{GottenFromSomewhere()};
    return SomeReallyLongLastingProcessingPipeline(someLocalVariable);
});

I am inclined to say that someLocalVariable will undoubtedly outlive the SomeReallyLongLastingProcessingPipeline() call, even if this is all happening in a lambda passed to std::async. Is this true?

I have to mention the std::future is inside an object that is constructed before SomeReallyLongLastingProcessingPipeline() and is destructed way after that function exits.

theodor96
  • 41
  • 8
  • 1
    I don't really think this is a duplicate of https://stackoverflow.com/questions/8437763/c-return-value-created-before-or-after-auto-var-destruction . My confusion here has to do with the `async` nature of the context. – theodor96 Feb 12 '19 at 15:15
  • 1
    I agree, the question would be the same if you didn't return the value and had an `std::future`. – Quentin Feb 12 '19 at 15:20
  • @theodor96 why you think a lambda would do something special than a regular function in this case? – apple apple Feb 12 '19 at 16:05
  • 1
    @appleapple as I said from that point of view it's a no-brainer that locals don't go out of scope until function return, but my concern was that `someLocalVariable` itself depends on something that goes out of scope at the end of the method where all of this snippet is located, and that could possibly mess things up, right? – theodor96 Feb 12 '19 at 17:01
  • @theodor96 I don't get it, you need to explicitly capture everything you used in lambda. In your example, the lambda can not use any local variable... – apple apple Feb 13 '19 at 14:28

1 Answers1

2

In itself the code posted by you seems ok and innocent, however a std::vector of char* made me suspicious. Your comment "...but my concern was that someLocalVariable itself depends on something that goes out of scope at the end of the method where all of this snippet is located, and that could possibly mess things up, right?" stresses my suspicion:

Yes someLocalVariable would outlive SomeReallyLongLastingProcessingPipeline but not necessarily the things you have pointed the char* in your std::vector to. Your problem is probably GottenFromSomewhere, which fills your someLocalVariable with pointers to things not alive when this whole lambda is executed. So it might live or might be already "dead" in the constructor of someLocalVariable and the same is true for SomeReallyLongLastingProcessingPipeline.

However this keeps speculation without your full code.

Use a std::vector<std::string> instead.


Update from comments:

#include <iostream>
#include <future>
#include <string>
#include <vector>
#include <memory>

bool SomeReallyLongLastingProcessingPipeline(std::vector<const char*> data) {
    return data.at(0)[0] == 'L';
}

//Prefer this one
bool SomeReallyLongLastingProcessingPipeline(std::vector<std::shared_ptr<const std::string>> data) {
    return data.at(0)->find('L');
}

std::future<bool> foo() {

    auto big_string_you_wont_change_until_lambda_finished = std::make_shared<std::string>("Long long text "
                                                                  "(>should be at least several dozen kB");
    //You could also use std::shared_ptr foo{new std::string("ff")}; but make_shared is safer (exception safety)

    //beware of lambda capture, DO NOT just use [&] or [&big_string_you_wont_change_until_lambda_finished]
    //use [=] or [big_string_you_wont_change_until_lambda_finished] is ok
    std::future<bool> result = std::async(std::launch::async, [big_string_you_wont_change_until_lambda_finished]()
    {
        std::vector<const char*> someLocalVariable{big_string_you_wont_change_until_lambda_finished->c_str()};
        std::vector<std::shared_ptr<const std::string>> someBetterLocalVariable
        {big_string_you_wont_change_until_lambda_finished};

        return SomeReallyLongLastingProcessingPipeline(someLocalVariable) || //I recommend the last one
        SomeReallyLongLastingProcessingPipeline(someBetterLocalVariable);
    });
    return result;
}

int main() {

    auto future = foo();

    std::cout << future.get() << "\n";

    return 0;
}   
Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • Indeed that is exactly what is going on. I would have used a `std::vector` but unfortunately I'm interacting with a C API taking a `char*` (not a `const char*`) and I'd rather avoid making a full copy but just trying to work around it. – theodor96 Feb 13 '19 at 09:53
  • Don't optimise early, it's very likely the copying of your C-strings into `std::string`s isn't even noticeable. Besides you have to if they go out of scope/aren't guaranteed to live until the lambda finishes. However, whats often used for stuff which has to be alive until lambdas run, are `std::shared_ptr`s. So you could use `std::shared_ptr`s. Make sure however you give your lambda a full copy of the shared ptr, they are not design to be taken by reference/pointer. Lifetime management with built-in/C-types gets really ugly and thrust me, isn't worth it. I write some example code – Superlokkus Feb 13 '19 at 10:10
  • Does `SomeReallyLongLastingProcessingPipeline` has to write to the strings pointed to by your `char*`? Can you change `SomeReallyLongLastingProcessingPipeline` to use a `std::string`? Otherwise I would use `std::vector` – Superlokkus Feb 13 '19 at 10:58
  • I'm not really sure why you use `std::shared_ptr`, but if it's for cheap copy, I would recommend move capture the string. – apple apple Feb 13 '19 at 14:38
  • @appleapple There are really more important things than to `std::move` a `std::shared_ptr`, the compiler will possibly even optimise it out. – Superlokkus Feb 13 '19 at 17:46
  • @appleapple I just made an experiment, if notably it just causes one jump and lock instruction more, but if you look at the instructions for the future, thats not the bottleneck. https://gcc.godbolt.org/z/7bFgy_ – Superlokkus Feb 13 '19 at 18:16
  • The `SomeReallyLongLastingProcessingPipeline` calls C APIs that take argc/argv-like arguments and even like to write to them, so using `std::string`s or therefore `std::shared_ptr`s to `std::string`s is not really an option. What I could have done is deep copy the `std::string::c_str()` into some `new char[]`, but as I said I was trying to avoid that – theodor96 Feb 14 '19 at 09:38
  • @Superlokkus well, I mean `[str = std::move(out_str)](){...}`, no `shared_ptr` – apple apple Feb 14 '19 at 10:09
  • @appleapple I already recommended to the OP in the first half of my answer, to simply use a `std::string`, which of course could be moved, but in the comments, he/she said he has to use a C API with built in strings/char*. However it seems to be an AB problem and he didn't replied yet, so I just made my 2 closest guess of what he said so far. – Superlokkus Feb 14 '19 at 11:05