【问题标题】:Efficiently creating a class inside a class method and passing it out of there?在类方法中有效地创建一个类并将其传递出去?
【发布时间】:2021-11-22 06:17:59
【问题描述】:
    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() 本地堆栈上的实例被复制(只有编译器知道在哪里)以持续足够长的时间以再次复制到“值”。

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

编辑:

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

编辑 2:

小测试项目。

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

【问题讨论】:

  • 你使用什么编译器?返回时的副本已在 C++11 (en.cppreference.com/w/cpp/language/copy_elision) 中删除。使用带有 emplace_back 的 std::string 和 std::vector 代替 CString 和 CList。
  • VS 2019 社区版。 (调试代码)。我可以在调试器中看到该列表被复制了两次。
  • IIRC 返回值优化成为 C++17 的强制要求。也许编译器设置被设置为 C++11(复制省略可能但不是强制性的)。
  • 嗯,这很奇怪。我尝试使用显示 ctor/dtor 用法的自定义 CList 类构建minimal reproducible example,并且在使用 MSVC 工具链时使用了一个复制 ctor(CLang 工具链从未使用过复制 ctor)。我正在使用 MSVC2019 社区版本 16.10.4 。换一种说法请提供minimal reproducible example...
  • 看起来调试版本仍会生成带有额外副本的代码。 (至少 Visual Studio 2019 是这样)

标签: c++ return-value-optimization


【解决方案1】:

我使用此代码运行了一些测试(使用 Visual Studio 2019),并观察到调试和发布版本的行为差异。调试构建 DO 调用复制构造函数并且不执行 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";
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-22
    • 2023-03-19
    • 2019-03-22
    • 2021-12-26
    • 2011-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多