c++ - 在类方法中有效地创建一个类并将其从那里传递出去?

标签 c++ return-value-optimization

    CList<CString> Split(char delim) {
        CList<CString> subStrings;
        for (char* ps = Buffer(); *ps; ps++) {
            char* pe;
            for (pe = ps; *pe && (*pe != delim); pe++)
                ;
            subStrings.Append (CString(ps, int (pe - ps)));
            if (!*pe)
                break;
            ps = pe;
        }
        return subStrings;
    }

    CList<CString> values = s.Split(';');

最后的语句导致列表的复制构造函数被调用两次:第一次是当 subString 从方法中传递出去时。 Split() 本地堆栈上的实例被复制(复制到只有编译器知道的位置)以持续足够长的时间,以便再次复制到“值”。

如果不从根本上更改我的代码(例如向 Split() 添加 CList& 参数),我将如何在此处不进行两个(相当昂贵的)列表复制操作?

编辑:

编译器标志设置为使用 c++20。

编辑2:

小型测试项目。

https://drive.google.com/file/d/1f1GVRJ9lbu2nYnhsLLv3YbCjtKr8m6-k/view?usp=sharing

最佳答案

我使用此代码(使用 Visual Studio 2019)运行了一些测试,并且观察到调试和发布版本中的行为差异。调试版本会调用复制构造函数,并且不会执行 RVO/复制省略。发布版本会进行优化,并且不会进行不必要的复制。此示例向您展示如何以最少的复制来拆分字符串(在发布版本中)

这个结果确实让我有些惊讶,所以感谢您提出这个问题。我今天确实学到了一些东西:)

发布版本的输出符合预期(emplace_back 也不复制):

-----------------------------------------------------
calling split function

my_list::my_list
  my_string::my_string
  my_string::my_string
  my_string::my_string
  my_string::my_string
-----------------------------------------------------
result of split

the
quick
brown
fox

-----------------------------------------------------
cleanup vector starting
my_list::~my_list
  my_string::~my_string
  my_string::~my_string
  my_string::~my_string
  my_string::~my_string

但是调试版本显然没有进行这种优化:

my_list::my_list
  my_string::my_string
  my_string::my_string
  my_string::my_string
  my_string::my_string
  my_string::my_string(const my_string&), copy constructor
  my_string::my_string(const my_string&), copy constructor
  my_string::my_string(const my_string&), copy constructor
  my_string::my_string(const my_string&), copy constructor
my_list::my_list(const my_list&), copy constructor
my_list::~my_list
  my_string::~my_string
  my_string::~my_string
  my_string::~my_string
  my_string::~my_string
-----------------------------------------------------
result of split

the
quick
brown
fox

-----------------------------------------------------
cleanup vector starting
my_list::~my_list
  my_string::~my_string
  my_string::~my_string
  my_string::~my_string
  my_string::~my_string

这是测试代码:

#include <iostream>
#include <vector>

struct my_string
{
    my_string(const char* from, const char* to) :
        value(from, to)
    {
        std::cout << "  my_string::my_string\n";
    }

    my_string(const my_string& rhs) :
        value{ rhs.value }
    {
        std::cout << "  my_string::my_string(const my_string&), copy constructor\n";
    }

    my_string(my_string&& rhs) :
        value{ std::move(rhs.value) }
    {
        std::cout << "  my_string::my_string(my_string&&), move constructor\n";
    }


    ~my_string()
    {
        std::cout << "  my_string::~my_string\n";
    }
 
    std::string value;
};


struct my_list
{
    my_list()
    {
        // note reserving some more room up front will reduce reallocations 
        // try commenting this out and you will see many more strings created/destroyed
        // because of vector reallocation
        strings.reserve(128);
        std::cout << "my_list::my_list\n";
    }

    my_list(const my_list& rhs) :
        strings{ rhs.strings }
    {
        std::cout << "my_list::my_list(const my_list&), copy constructor\n";
    }

    ~my_list()
    {
        std::cout << "my_list::~my_list\n";
    }

    std::vector<my_string> strings;
};


my_list split(const char* string, char delim)
{
    my_list list;

    for (const char* ps = string; *ps != 0; ++ps)
    {
        const char* pe{ ps };
        while ((*pe != 0) && (*pe != delim)) 
        {
            ++pe;
        }
    
        list.strings.emplace_back(ps,pe);
        ps = pe;
    }

    return list;
}

int main()
{
    std::cout << "-----------------------------------------------------\n";
    std::cout << "calling spit function\n\n";

    {
        auto mylist = split("the,quick,brown,fox", ',');

        std::cout << "-----------------------------------------------------\n";
        std::cout << "result of split \n\n";

        for (const auto& mystring : mylist.strings)
        {
            std::cout << mystring.value << "\n";
        }

        std::cout << "\n-----------------------------------------------------\n";
        std::cout << "cleanup vector starting";
    }
}

关于c++ - 在类方法中有效地创建一个类并将其从那里传递出去?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69391661/

相关文章:

c++ - 为什么我们需要访问者模式中的accept()以及为什么我们不能直接调用visitor.visit()?

c++ - 无法在 C++ 中使用 ReadConsoleInput 读取鼠标事件

c++ - 将笛卡尔(x,y,z)转换为圆柱(ρ,θ,z)坐标2D/3D的代码

C++ 返回一个对象抛出一个接口(interface)

c++ - 禁用 g++ 的返回值优化

c++ - 什么是复制省略和返回值优化?

c++ - 标签中整数的值(Qt)

c++ - Visual Studio 2012 - C MariaDB 客户端 - 错误 LNK2019 : unresolved external symbol _mysql_init@4

c++ - 基于函数返回的STL容器构建效率

c++ - 什么是复制省略和返回值优化?