Чтобы решить вашу проблему, я переписал ее.
#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 могут использовать для возврата возвращаемых значений (ну, они не поддерживают лямбду, но вы поняли).