简体   繁体   中英

Will (N)RVO be applied with my function in this situation?

I have the following code: (ok, in reality it's much more complicated, but I simplified it to make it easier to understand. so please disregard the things that seems stupid. I can't change them in my real situation)

#include <string>

using std::string;

ReportManager g_report_generator;

struct ReportManager
{
    // I know, using c_str in this case is stupid. 
    // but just assume that it has to be this way
    string GenerateReport() { string report("test"); return report.c_str(); }
}

string DoIt(bool remove_all)
{
    if(g_report_generator.isEmpty())
        return string();

    string val = g_report_generator.GenerateReport();

    if(remove_all)
        g_report_generator.clear();

    return val;
}

void main()
{
    string s = DoIt(true);
}

Will (N)RVO be applied with my functions? I did a bit of research, and it would seem like it, but I'm not really convinced and I'd like a second opinion (or more).

I'm using Visual Studio 2017.

To solve your problem I rewrote it.

#include <string>


struct string : std::string {
    using std::string::string;

    string(string&& s) {
        exit(-1);
    }
    string(string const&) {
        exit(-2);
    }

    string() {}
};

struct ReportManager
{
    // I know, using c_str in this case is stupid. 
    // but just assume that it has to be this way
    string GenerateReport()
    {
        string report("test");
        return report.c_str();
    }
    bool isEmpty() const { return true; }
    void clear() const {}
};

ReportManager g_report_generator;

string DoIt(bool remove_all)
{
    if(g_report_generator.isEmpty())
        return string();

    string val = g_report_generator.GenerateReport();

    if(remove_all)
        g_report_generator.clear();

    return val;
}

int main()
{
    string s = DoIt(true);
}

The trick with this rewriting is that elision permits skipping copy/move ctors. So every time we actually copy an object (even if inlined), we'll insert an exit clause; only by elision can we avoid it.

GenerateReport has no (N)RVO or any kind of elision, other than possibly under as-if. I doubt a compiler will be able to prove that, especially if the string is non-static and large enough to require heap storage.

For DoIt both NRVO and RVO is possible. Elision is legal there, even with side effects.

MSVC fails -- notice calls to ??0string@@QAE@$QAU0@@Z , which is the move constructor of my local string class.

When I force the possible RVO case to run by saying it is empty , you'll see that the compiler also fails to RVO optimize here; there is an exit(-1) inlined into the disassembly.

Clang manages to RVO the return string(); but not NRVO the return val; .

By far the easiest fix is:

string DoIt(bool remove_all)
{
    if(g_report_generator.isEmpty())
        return string();

    return [&]{   
      string val = g_report_generator.GenerateReport();

      if(remove_all)
        g_report_generator.clear();

      return val;
    }();
}

which has double RVO, and a lambda that does simple NRVO. Zero structural changes to your code, and functions which C++98 compilers can elide return values on (well, they don't support lambda, but you get the idea).

I do not think (N)RVO is possible in either functions. GenerateReport has to construct a string from character array, there is nothing left for NRVO. DoIt returns two different values through it control path, which makes it impossible to perform NRVO as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM