I'm trying to get better at understanding the intricacies of C . I came across this exercise in C Best Practices by Jason Turner.
The question is if a copy is being created here.
std::map<std::string, int> get_map();
using element_type = std::pair<std::string, int>;
for (const element_type & : get_map())
{
}
My answer is yes, because I think std::map isn't explicitly using std::pair to represent the key-value pair. So maybe implicitly the underlying key-value pair is being copied to element_type? How off am I here? Any help would be appreciated :).
CodePudding user response:
i try this simple code section:
#include <map>
#include <string>
#include <iostream>
std::map<std::string, int> get_map()
{
std::map<std::string, int> m { {"CPU", 10}, {"GPU", 15} };
return m;
}
int main()
{
using element_type = std::pair<std::string, int>;
for (const element_type &e : get_map())
{
}
return 1;
}
It does NOT copy, insteady the move is moved. i use https://cppinsights.io/ to get:
std::map<std::basic_string<char>, int, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, int> > > get_map()
{
std::map<std::string, int> m = std::map<std::basic_string<char>, int, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, int> > >{std::initializer_list<std::pair<const std::basic_string<char>, int> >{std::pair<const std::basic_string<char>, int>{"CPU", 10}, std::pair<const std::basic_string<char>, int>{"GPU", 15}}, std::less<std::basic_string<char> >(), std::allocator<std::pair<const std::basic_string<char>, int> >()} /* NRVO variable */;
return std::map<std::basic_string<char>, int, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, int> > >(static_cast<std::map<std::basic_string<char>, int, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, int> > > &&>(m));
}
int main()
{
using element_type = std::pair<std::basic_string<char>, int>;
{
std::map<std::basic_string<char>, int, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, int> > > && __range1 = get_map();
std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, int> > __begin1 = __range1.begin();
std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, int> > __end1 = __range1.end();
for(; operator!=(__begin1, __end1); __begin1.operator ()) {
const std::pair<std::basic_string<char>, int> & e = std::pair<std::basic_string<char>, int>(__begin1.operator*());
}
}
return 1;
}
Hope this help. thx.
CodePudding user response:
The for
loop will use an iterator internally. Dereferencing an iterator will generate a value_type
object not a reference to one. So even though your own loop specifies a reference, it will be a reference to a temporary object that was created (via copy) by the iterator and kept alive by your reference.
CodePudding user response:
In my answer, I'd like to address the question title literally:
How to know when a copy is being created?
This can be achieved quite easy with a sample class where every constructor in quest makes an output:
struct Data {
Data() { std::cout << "Data::Data()\n"; }
Data(const Data&) { std::cout << "Data::Data(const Data&)\n"; }
Data& operator=(const Data&) { std::cout << "Data::operator=(const Data&)\n"; return *this; }
Data(Data&&) { std::cout << "Data::Data(Data&&)\n"; }
Data& operator=(Data&&) { std::cout << "Data::operator=(Data&&)\n"; return *this; }
};
According to cppreference.com, the copy constructor of std::pair is the default, i.e. it just calls the copy constructors of its member variables:
pair( const pair& p ) = default;
Thus, a little MCVE can illustrate the mistake of the OP quite obviously:
#include <iostream>
#include <map>
struct Data {
Data() { std::cout << "Data::Data()\n"; }
Data(const Data&) { std::cout << "Data::Data(const Data&)\n"; }
Data& operator=(const Data&) { std::cout << "Data::operator=(const Data&)\n"; return *this; }
Data(Data&&) { std::cout << "Data::Data(Data&&)\n"; }
Data& operator=(Data&&) { std::cout << "Data::operator=(Data&&)\n"; return *this; }
};
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ ; std::cout << std::endl
int main()
{
DEBUG(std::map<int, Data> aMap = { { 1, Data() }, { 2, Data() } });
// done like OP -> copy assignment
DEBUG(for (const std::pair<int, Data>& value : aMap) {
std::cout << value.first << '\n';
});
// without copy assignment
DEBUG(for (const std::pair<const int, Data>& value : aMap) {
std::cout << value.first << '\n';
});
DEBUG(for (const std::map<int, Data>::value_type& value : aMap) {
std::cout << value.first << '\n';
});
}
Output:
std::map<int, Data> aMap = { { 1, Data() }, { 2, Data() } };
Data::Data()
Data::Data(Data&&)
Data::Data()
Data::Data(Data&&)
Data::Data(const Data&)
Data::Data(const Data&)
for (const std::pair<int, Data>& value : aMap) { std::cout << value.first << '\n'; };
Data::Data(const Data&)
1
Data::Data(const Data&)
2
for (const std::pair<const int, Data>& value : aMap) { std::cout << value.first << '\n'; };
1
2
for (const std::map<int, Data>::value_type& value : aMap) { std::cout << value.first << '\n'; };
1
2
Thus, for the first loop (using std::pair<int, Data>
) the copy constructor of Data
is called in each iteration.
This doesn't happen for std::pair<const int, Data>
nor for the std::map<int, Data>::value_type
.
Another simple alternative could have been the usage of auto
:
for (const auto& value : aMap) {
std::cout << value.first << '\n';
}
Furthermore, I would like to mention the diagnostic of g with an even shorter MCVE:
#include <iostream>
#include <map>
struct Data { };
int main()
{
std::map<int, Data> aMap = { { 1, Data() } };
for (const std::pair<int, Data>& value : aMap) std::cout << value.first << '\n';
}
Output:
main.cpp: In function 'int main()':
main.cpp:9:36: warning: loop variable 'value' of type 'const std::pair<int, Data>&' binds to a temporary constructed from type 'std::pair<const int, Data>' [-Wrange-loop-construct]
9 | for (const std::pair<int, Data>& value : aMap) std::cout << value.first << '\n';
| ^~~~~
main.cpp:9:36: note: use non-reference type 'const std::pair<int, Data>' to make the copy explicit or 'const std::pair<const int, Data>&' to prevent copying