I ran some tests with this code (using visual studio 2019), and I observe differences in behavior in debug and release builds. The debug builds DO call copy constructors and don't do RVO/copy elision. Release builds do the optimization and no unecessary copying is done. This example shows you how to split strings with minimal copying (in release builds)
This result did surprise me somewhat, so thanks for asking this question. I did learn something to today :)
The output of the release build is as expected (emplace_back doesn't copy either):
-----------------------------------------------------
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
However the debug build clearly doesn't do that kind of optimization :
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
This is the test code :
#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";
}
}