Будет ли (N) RVO применяться с моей функцией в этой ситуации? - PullRequest
0 голосов
/ 23 января 2019

У меня есть следующий код: (хорошо, на самом деле все гораздо сложнее, но я упростил его, чтобы было легче понять. Поэтому, пожалуйста, не обращайте внимания на вещи, которые кажутся глупыми. Я не могу изменить их в моей реальной ситуации)

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

Будет ли (N) RVO применяться с моими функциями?Я провел небольшое исследование, и, похоже, это так, но я не совсем уверен, и мне хотелось бы получить второе мнение (или больше).

Я использую Visual Studio 2017.

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Я не думаю, что (N) RVO возможен в обеих функциях. GenerateReport должен построить строку из массива символов, для NRVO ничего не осталось. DoIt возвращает два разных значения через этот путь управления, что также делает невозможным выполнение NRVO.

0 голосов
/ 23 января 2019

Чтобы решить вашу проблему, я переписал ее.

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

Хитрость в этом переписывании заключается в том, что elision позволяет пропускать копии / перемещать ctors. Поэтому каждый раз, когда мы на самом деле копируем объект (даже если он встроен), мы вставляем предложение exit; мы можем избежать этого только благодаря избранию.

GenerateReport не имеет (N) RVO или какого-либо иного права, кроме, как, возможно, в рамках «как будто». Я сомневаюсь, что компилятор сможет доказать это, особенно если строка не является статичной и достаточно большой, чтобы требовать кучи.

Для DoIt возможны как NRVO, так и RVO. Elision там законен, даже с побочными эффектами.

MSVC не удается - уведомление о вызовах ??0string@@QAE@$QAU0@@Z, который является конструктором перемещения моего локального string класса.

Когда я принудительно запускаю возможный случай RVO , говоря, что он пуст , вы увидите, что компилятор также не может выполнить оптимизацию RVO здесь; exit(-1) встроен в разборку.

Clang управляет RVO return string();, но не NRVO return val;.

Самым простым решением является:

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;
    }();
}

с двойным RVO и лямбда с простым NRVO. Нулевые структурные изменения в вашем коде и функции, которые компиляторы C ++ 98 могут использовать для возврата возвращаемых значений (ну, они не поддерживают лямбду, но вы поняли).

...