Why is the signature push_back void push_back (const value_type & val) not void push_back (value_type val)?

Why is the signature of the push_back function next?

 void push_back (const value_type& val); 

The missing value is copied to the container, why not make a copy directly in the argument list?

 void push_back (value_type val); 
+7
c ++ stl
source share
4 answers

The answer is not to make another copy. Take a look at this simple example illustrating the difference between using value_type and const value_type& .

 #include <iostream> using namespace std; struct A { A() {} A(A const& copy) { cout << "Came to A::A(A const& copy)\n"; } void print() const { cout << "Came to A:print()\n"; } }; void foo(A const& a) { A copy = a; copy.print(); } void bar(A a) { A copy = a; copy.print(); } int main() { A a; foo(a); bar(a); } 

The result of starting the program:

  Came to A :: A (A const & copy)
 Came to A: print ()
 Came to A :: A (A const & copy)
 Came to A :: A (A const & copy)
 Came to A: print ()

Note the extra call to the copy constructor due to the call to bar . For some objects, this additional copy construction and corresponding destruction can be very expensive when the operation is performed millions of times.

+2
source share

Here's what an extremely simplified push_back in a vector might seem when implemented with each of these interfaces:

 // by reference void push_back (value_type const & val) { // Copy val into its designated place. new (m_data_ptr + m_len++) value_type (val); } // by value void push_back (value_type val) { // Copy val into its designated place. // In C++11, this copy may not happen if value_type is movable. But that's // not always the case. (you have to use std::move too.) new (m_data_ptr + m_len++) value_type (val); } 

They look the same, don't they?

The problem is when you try to call them, in particular, by default:

 string s; ... v.push_back (s); 

If push_back accepts its parameter by reference (i.e. value_type & val ), then a reference to the existing object s is passed to the function, and no copies are made here. Of course, we still need one copy inside the function, but this is necessary.

However, if push_back written to get its parameter by value (i.e. value_type val ), then a copy of the string s will be made right on the call site, on the stack and in the argument, which will be called val . Here val not a reference to a string, it is a string, and it should appear from somewhere. This extra copy made the STL designer (s) and the most reasonable C ++ libraries accept pass-by-reference as the preferred choice for many situations (and if you're curious what const is there to tell the caller what now when this function can change your precious object, since the link to it is passed to the function, it will not!)

By the way, this discussion mainly refers to C ++ 98 (i.e., the old C ++). Current C ++ has Rvalue links and movement and perfect redirection, which provide more interface features and the possibility of cleaner and more accurate and more efficient interfaces / implementations, but also make this question a little more complicated.

In C ++ 11, there are two push_back overloads (as well as a new emplace_back member) in vector and other containers.

push_back :

 void push_back (value_type const & val); void push_back (value_type && val); 

The second is the correct version of what you are suggesting (i.e. it will not be ambiguous for the compiler). It allows the implementation to move the value from this rvalue link and allows the compiler to generate code to call a faster version if necessary.

For backward compatibility reasons (and possibly a few other minor ones), the old push_back signature cannot be removed from C ++.

+1
source share

Saving a movable and copyable class

Imagine you have this class:

 class Data { public: Data() { } Data(const Data& data) { std::cout << " copy constructor\n";} Data(Data&& data) { std::cout << " move constructor\n";} Data& operator=(const Data& data) { std::cout << " copy assignment\n"; return *this;} Data& operator=(Data&& data) { std::cout << " move assignment\n"; return *this;} }; 

Note that a good C ++ 11 compiler should define all these functions for you ( Visual Studio does not ), but I define them here to output debugging.

Now, if you want to write a class to store one of these classes, I can use pass-by-value, as you suggest:

 class DataStore { Data data_; public: void setData(Data data) { data_ = std::move(data); } }; 

I use C ++ 11 to move semantics to move the value to the right place. Then I can use this DataStore as follows:

  Data d; DataStore ds; std::cout << "DataStore test:\n"; ds.setData(d); std::cout << "DataStore test with rvalue:\n"; ds.setData(Data{}); Data d2; std::cout << "DataStore test with move:\n"; ds.setData(std::move(d2)); 

Which has the following conclusion:

 DataStore test: copy constructor move assignment DataStore test with rvalue: move assignment DataStore test with move: move constructor move assignment 

This is normal. I have two moves in the last test, which may not be optimal, but the moves are usually cheap, so I can live with it. To make it more optimal, we will need to overload the setData function, which we will do later, but perhaps premature optimization at this stage.

Saving an unreadable class

But now imagine that we have a copyable but immutable class:

 class UnmovableData { public: UnmovableData() { } UnmovableData(const UnmovableData& data) { std::cout << " copy constructor\n";} UnmovableData& operator=(const UnmovableData& data) { std::cout << " copy assignment\n"; return *this;} }; 

Prior to C ++ 11, all classes were immutable, so expect to find many of them in the wild today. If I need to write a class to store this, I cannot use the move semantics, so probably I would write something like this:

 class UnmovableDataStore { UnmovableData data_; public: void setData(const UnmovableData& data) { data_ = data; } }; 

and pass by reference to const. When I use it:

  std::cout << "UnmovableDataStore test:\n"; UnmovableData umd; UnmovableDataStore umds; umds.setData(umd); 

I get the output:

 UnmovableDataStore test: copy assignment 

with one copy, as you would expect.

Keeping an Uncovered Class

You can also have a movable but non-copyable class:

 class UncopyableData { public: UncopyableData() { } UncopyableData(UncopyableData&& data) { std::cout << " move constructor\n";} UncopyableData& operator=(UncopyableData&& data) { std::cout << " move assignment\n"; return *this;} }; 

std::unique_ptr is an example of a movable but not std::unique_ptr class. In this case, I will probably write a class to store it as follows:

 class UncopyableDataStore { UncopyableData data_; public: void setData(UncopyableData&& data) { data_ = std::move(data); } }; 

where I pass the rvalue reference and use it like this:

  std::cout << "UncopyableDataStore test:\n"; UncopyableData ucd; UncopyableDataStore ucds; ucds.setData(std::move(ucd)); 

with the following output:

 UncopyableDataStore test: move assignment 

and note that we have only one move, which is good.

Common containers

However, STL containers must be shared, they must work with all types of classes and be as optimal as possible. And if you really need the overall data warehousing implementation above, it might look like this:

 template<class D> class GenericDataStore { D data_; public: void setData(const D& data) { data_ = data; } void setData(D&& data) { data_ = std::move(data); } }; 

Thus, we get the best performance, whether we use uncoated or incompatible classes, but we must have at least two setData overloads that can lead to code duplication. Using:

  std::cout << "GenericDataStore<Data> test:\n"; Data d3; GenericDataStore<Data> gds; gds.setData(d3); std::cout << "GenericDataStore<UnmovableData> test:\n"; UnmovableData umd2; GenericDataStore<UnmovableData> gds3; gds3.setData(umd2); std::cout << "GenericDataStore<UncopyableData> test:\n"; UncopyableData ucd2; GenericDataStore<UncopyableData> gds2; gds2.setData(std::move(ucd2)); 

Output:

 GenericDataStore<Data> test: copy assignment GenericDataStore<UnmovableData> test: copy assignment GenericDataStore<UncopyableData> test: move assignment 

Live demo. Hope this helps.

+1
source share

A few reasons:

  • One instance has already been made to this class. If you do not have const value_type& val , you will force another copy. Passing it by reference ( value_type& ) will help you with this.
  • This also tells the compiler that val cannot be changed in any way. This is done by making it `const '
  • Of course, once you make a copy, you are allowed to change this, but val cannot be changed in any way, and the declaration of the function ensures that
0
source share

All Articles