Re: New tricks in C++11 that you have found?
Jorgen Grahn <grahn+nntp@snipabacken.se> wrote:
Given a piece of everyday and well-written C++98 code[1], what constructs
would a C++11 guy want to replace because they are tedious, error-prone
or just look archaic?
That's a completely different question.
Many of the new constructs not only add shortcuts and easier ways of
doing things, but in fact also add to the expressiveness of the language.
The second example in my original post was a good example of that. At
first glance the ranged for-loop looks simply like syntactic sugar and
little else. However, it hides quite a lot of expressiveness inside it.
In the example in question, the crucial code was like this:
template<typename Container_t>
void foo(const Container_t& container)
{
for(auto& element: container)
...
}
Note how the function will work with any of the STL containers,
static arrays and initializer lists. (Technically speaking it's
possible to make the above function work in the same way with C++98
(except for initializer lists, of course), but it requires more code.)
This example might still sound a bit artificial, though. The above
example function is not something that's very common in actual code.
However, there are other similar situations that are much more common,
and where the abstraction provided by the range-based for loop can
actually be beneficial.
For example, suppose that you have, let's say, a std::vector as a
member of a class, and several methods of that class to through the
entire array to perform operations on its members (or using their
values). If you later decide that the std::vector can in fact be
changed to a static array for efficiency, in C++98 you would have
to go through all the code that loops through the vector and change
it (if for nothing else, to change the call to the size() method, or
if you loop using iterators, to change them to pointers). However, if
you had used range-based for loops, you would not need to change the
existing code.
In typical C++98 code a well-designed class has its copy constructor
and assignment operator disabled, if copying the class would be a
heavy operation that is not needed. (Implementing working copy
constructors and assignment operators for a class that handles
dynamically allocated resources can be laborious, error-prone, and
in many cases unneeded because the code using the class often doesn't
need support for that anyways.)
Rvalue references and copy constructors allow lifting that limitation
to a degree, without compromising the efficiency of the program. Now
the code using that class can make copies in certain situations
(such as returning an object from a function, even in situations where
the compiler is unable to perform a return-value-optimization trick)
without having to worry about efficiency issues. Creating temporaries
and passing them around by value will become efficient and is often
much cleaner than when copying has been disables completely. And the
code doesn't even have to pay overhead costs of techniques like
copy-on-write or reference counting.
Not being able to specify compile-time constants in an array has
always been a severe limitation of C++98, one that I have encountered
in actual programs. C++11 lifts that limitation, which is nice.
Many of the new convenience features help make the code cleaner.
For instance, if you wanted to make a small struct (which should be
as efficient as possible) where you want to make sure that all members
are properly initialized, in C++98 you have to write an inline constructor
for the struct, like:
struct Something
{
int a, b, c;
Something(): a(1), b(10), c(25) {}
};
This is not horrible, but it's always a slight nuisance. In C++11,
however, it becomes much cleaner:
struct Something
{
int a = 1, b = 10, c = 25;
};
(Moreover, in C++98 there's no way to initialize a member static array
even with a constructor initialization list, while in C++11 you can do
it "inline" like that, or in the constructor initialization list if you
like.)
The 'auto' keyword not only can make things cleaner, like this:
std::map<std::string, std::string> theMap;
auto iter = theMap.begin();
...
but can also be actually useful in abstracting the code, like so:
auto someFunctor = boost::bind(&some_function, _2, _1, some_object);
(In this example we simply don't care, nor should have to care,
what the type returned by std::bind is, so the 'auto' keyword fits
perfectly for this purpose.)