Refactoring with C ++ 11

Given the new set of tools provided by C ++, many programmers, trying to simplify code, expressiveness, efficiency, slip through their old code and do tricks (some meaningless, some successful) to achieve their goals. Without trying to lose too much time on such works and just make non-intrusive and self-sufficient changes, what are the best practices?

Let me cross out the obvious:

  • Use auto to start iterator-based loops:

    for (std::vector<foo>::const_iterator it(lala.begin()), ite(lala.end()); it != ite; ++it); // becomes for (auto it(lala.cbegin()), ite(lala.cend()); it != ite; ++it); 
  • Use relationships for multiple assignments that simply create lines of C code ( how do I assign multiple values ​​to a structure at once? )

     a = 1; b = 2; c = 3; d = 4; e = 5; // becomes std::tie(a, b, c, d, e) = std::make_tuple(1, 2, 3, 4, 5); 
  • To make a class non-inheritable, simply declare it β€œfinal” and remove the code that has reached this behavior http://www.parashift.com/c++-faq/final-classes.html

  • Use the delete keyword to explicitly hide constructors / destructors instead of declaring them private (for example, code to create objects with a bunch of non-copied objects, etc.)

  • Turn trivial functors created just to simplify the execution of one STL algorithm in lambda functions (in addition to reducing clutter of code, you will have guaranteed built-in calls)

  • Simplify the flow of RAII objects with a smart pointer

  • Get rid of bind1st, bind2nd and just use bind

  • Replace manually written code for type types (Is_ptr_but_dont_call_for_const_ptrs <> and such :)) with the standard code provided by type_traits>

  • Stop enabling forward headers for functionality now nested in STL (BOOST_STATIC_ASSERT vs static_assert)

  • Provide semantics of moving to classes (although this will not be considered dirty / quick / easy change)

  • Use nullptr , if possible, instead of the NULL macro and get rid of the code that fills the containers of pointers with 0 cast from the type of the object

     std::vector<foo*> f(23); for (std::size_t i(0); i < 23; ++i) { f[i] = static_cast<foo*>(0); } // becomes std::vector<foo*> f(23, nullptr); 
  • Clear data access syntax for data

     std::vector<int> vec; &vec[0]; // access data as a C-style array vec.data(); // new way of saying the above 
  • Replace throw () with noexcept (besides throwing an outdated exception, you get some speed advantages http://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler @ 00.29.42)

     void some_func() noexcept; // more optimization options void some_func() throw(); // fewer optimization options void some_func() ; // fewer optimization options 
  • Replace the code in which you clicked tempory in the container and hoped that the optimizer would be removed from the copy, with the "emplace" function, where possible, to perfectly forward the argument and build the object directly into the container without time.

     vecOfPoints.push_back(Point(x,y,z)); // so '03 vecOfPoints.emplace_back(x, y, z); // no copy or move operations performed 

UPDATE

The answer of Shafik Yakhmur was rightfully awarded for his generosity because he received the greatest recognition from the audience.

R Sahu's answer was my accepted, because the combination of functions he offers is breathtaking in refactoring : to make the code more understandable and clean, simpler and more elegant.

+63
c ++ c ++ 11 refactoring
Feb 02 '14 at 19:09
source share
11 answers

I would add the delegation of constructors and initializers of class members to the list.

Simplification by delegating constructors and initializing within a class

C ++ 03:

 class A { public: // The default constructor as well as the copy constructor need to // initialize some of the members almost the same and call init() to // finish construction. A(double data) : id_(0), name_(), data_(data) {init();} A(A const& copy) : id_(0), name_(), data_(copy.data_) {init();} void init() { id_ = getNextID(); name_ = getDefaultName(); } int id_; string name_; double data_; }; 

C ++ 11:

 class A { public: // With delegating constructor, the copy constructor can // reuse this constructor and avoid repetitive code. // In-line initialization takes care of initializing the members. A(double data) : data_(data) {} A(A const& copy) : A(copy.data_) {} int id_ = getNextID(); string name_ = getDefaultName(); double data_; }; 
+16
Apr 2 '14 at 5:50
source share

1. Rand replacement

One of the big winnings in C ++ 11 is to replace the use of rand() all the parameters available in a random header . Replacing rand() in many cases should be straightforward.

Stefan T. Lawave probably made this moment the strongest with his presentation of rand () considered harmful . The examples show a uniform integer distribution from [0,10] using rand() :

 #include <cstdlib> #include <iostream> #include <ctime> int main() { srand(time(0)) ; for (int n = 0; n < 10; ++n) { std::cout << (rand() / (RAND_MAX / (10 + 1) + 1)) << ", " ; } std::cout << std::endl ; } 

and using std :: uniform_int_distrubution :

 #include <iostream> #include <random> int main() { std::random_device rd; std::mt19937 e2(rd()); std::uniform_int_distribution<> dist(0, 10); for (int n = 0; n < 10; ++n) { std::cout << dist(e2) << ", " ; } std::cout << std::endl ; } 

Along with this, we should move from std :: random_shuffle to std :: shuffle , which comes from the efforts to Cancel rand and friends . This was recently covered in a SO question. Why are std :: shuffle methods deprecated in C ++ 14? .

Please note that distributions are not guaranteed for different platforms .

2. Using std :: to_string instead of std :: ostringstream or sprintf

C ++ 11 provides std :: to_string , which can be used to convert numbers to std :: string , this will create content as equivalent to std :: sprintf . Most likely, this will be used instead of std :: ostringstream or snprintf . This is more convenient, there is probably not much difference in performance, and we can see from the Fast integer for converting strings to C ++ . probably much faster alternatives if performance is a major issue:

 #include <iostream> #include <sstream> #include <string> int main() { std::ostringstream mystream; mystream << 100 ; std::string s = mystream.str(); std::cout << s << std::endl ; char buff[12] = {0}; sprintf(buff, "%d", 100); std::string s2( buff ) ; std::cout << s2 << std::endl ; std::cout << std::to_string( 100 ) << std::endl ; } 

3. Using constexpr instead of template metaprograms

If you are dealing with literals, there may be times when using constexpr functions over a template metaprogram can lead to a clearer and possibly compiler code. Article Want speed? Using metaprogramming constexpr! gives an example of determining a prime using template metaprograms:

 struct false_type { typedef false_type type; enum { value = 0 }; }; struct true_type { typedef true_type type; enum { value = 1 }; }; template<bool condition, class T, class U> struct if_ { typedef U type; }; template <class T, class U> struct if_<true, T, U> { typedef T type; }; template<size_t N, size_t c> struct is_prime_impl { typedef typename if_<(c*c > N), true_type, typename if_<(N % c == 0), false_type, is_prime_impl<N, c+1> >::type >::type type; enum { value = type::value }; }; template<size_t N> struct is_prime { enum { value = is_prime_impl<N, 2>::type::value }; }; template <> struct is_prime<0> { enum { value = 0 }; }; template <> struct is_prime<1> { enum { value = 0 }; }; 

and using constexpr functions:

 constexpr bool is_prime_recursive(size_t number, size_t c) { return (c*c > number) ? true : (number % c == 0) ? false : is_prime_recursive(number, c+1); } constexpr bool is_prime_func(size_t number) { return (number <= 1) ? false : is_prime_recursive(number, 2); } 

The constexpr version is much shorter, easier to understand and, apparently, much better than the implementation of template metaprograms.

4. Using class member initialization to provide default values

As recently discussed in, Was a new C ++ 11 member initialization function introduced in an declaration made by initializing obsolete lists? to initialize a class member to provide default values ​​and may simplify cases where a class has multiple constructors.

Bjarne Stroustrup is a good example in the C ++ 11 FAQ, he says:

This saves a bit of input, but the real benefits come in classes with multiple constructors. Often, for all constructors, a regular member initializer is used:

and provides an example of elements that share a common initializer:

 class A { public: A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(D d) : a(7), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {} int a, b; private: HashingFunction hash_algorithm; // Cryptographic hash to be applied to all A instances std::string s; // String indicating state in object lifecycle }; 

and says:

The fact that hash_algorithm and s each has one default is lost in a mess of code and can easily become a problem during maintenance. Instead, we can eliminate the initialization of data members:

 class A { public: A(): a(7), b(5) {} A(int a_val) : a(a_val), b(5) {} A(D d) : a(7), b(g(d)) {} int a, b; private: HashingFunction hash_algorithm{"MD5"}; // Cryptographic hash to be applied to all A instances std::string s{"Constructor run"}; // String indicating state in object lifecycle }; 

Note that in C ++ 11, a class that uses a class member in initializers is no longer an aggregate , although this restriction has been removed in C ++ 14.

5. Use integer fixed-width types from cstdint instead of manual typedefs

Since the C ++ 11 standard uses C99 as a normative reference, we get fixed widths of integer types . For example:

 int8_t int16_t int32_t int64_t intptr_t 

Although some of them are optional, for exact integers of width the following applies from section C99 7.18.1.1 :

These types are optional. However, if the implementation provides integer types with a width of 8, 16, 32, or 64 bits, without padding bits and (for signed types) that have a double padding representation, it must define the appropriate typedef names.

+27
Feb 03 '14 at 3:19
source share

For each syntax:

 std::vector<int> container; for (auto const & i : container) std::cout << i << std::endl; 
+11
Feb 02 '14 at 20:35
source share

Use uniform initialization syntax to initialize variables

 widget w(x); // old widget w{x}; // new 

to avoid issues like C ++ the most unpleasant parsing (the rest of the reasons why the new method is superior are explained in a related article by Herb Sutter)

+7
Mar 30 '14 at 13:10
source share
  • Changing std::map to std::unordered_map and std::set to std::unordered_set , where ever the order of the elements of a container does not matter, significantly improves performance.
  • Using std::map::at instead of using square bracket syntax when you want to avoid involuntary insertions.
  • Use alias patterns if you want typedef patterns.
  • Using initialization lists instead of loops to initialize STL containers.
  • Replace arrays with fixed size C with std :: array.
+7
Apr 04 '14 at 10:37
source share

Function: std :: move

"Indicate a clear difference between copying and moving resources"

 std::string tmp("move"); std::vector<std::string> v; v.push_back(std::move(tmp)); //At this point tmp still be the valid object but in unspecified state as // its resources has been moved and now stored in vector container. 
+6
Mar 29 '14 at 21:02
source share

This blog post suggests Rule of Zero if all the owners in the class follow the RAII principle, letting you get rid of the Three / Four / Five rule in C ++ 11.

However, Scott Meyers shows here that the destructor is clearly not writing, copy / move and assignment constructors can cause subtle problems if you slightly modify your code (say for debugging). He then recommends explicitly declaring default (C ++ 11 function) the following functions:

 ~MyClass() = default; MyClass( const MyClass& ) = default; MyClass( MyClass&& ) = default; MyClass& operator=( const MyClass& ) = default; MyClass& operator=( MyClass&& ) = default; 
+6
Mar 31 '14 at 7:58
source share

Optimize simple math functions with constexpr, especially if they are called inside internal loops. This will allow the compiler to calculate them during compilation, saving time.

Example

 constexpr int fibonacci(int i) { return i==0 ? 0 : (i==1 ? 1 : fibonacci(i-1) + fibonacci(i-2)); } 

Another example is the use of std::enable_if to limit the valid types of template parameters in a specific function / class of the template. This would make your code more secure (unless you use SFINAE to limit the possible template arguments in your old code) when you mean some property like templates, and this is just one additional line of code

example:

 template < typename T, std::enable_if< std::is_abstract<T>::value == false, bool>::type = false // extra line > void f(T t) { // do something that depends on the fact that std::is_abstract<T>::value == false } 

Update 1: If you have a small array, the size of which is known at compile time, and you want to avoid the overhead of allocating heaps in std :: vector (which means: you want the array to be on the stack), you are only a choice in C + + 03 consisted of using c-style arrays. Change this to std::array . This is a simple change that provides you with a lot of functionally present in the stack distribution of std :: vector + (much faster than heap allocation, as I said earlier).

+5
Apr 02
source share

Use smart pointers. Please note that in some cases there are still good reasons to have a bare pointer, the best way to check if the pointer should be smart is to look for delete in it.

There should be no reason to use new . Replace each new with make_shared or make_unique .

Unfortunately, make_unique did not do this in the C ++ 11 standard , the best IMO solution is to implement it (see the previous link) and put some macros to check the __cplusplus version ( make_unique is available in C ++ 14).

Using make_unique and make_shared really important to ensure the security of the code.

+3
Apr 03 '14 at 22:22
source share
  • Prefer listings with reference to unoccupied listings

    • In C ++ 98 enumerations, there are no enumerations for enums, such as the following code snippet. The names of such counters refer to the area containing the enumeration, namely, nothing in this area can have the same name.

       enum Color{ blue, green, yellow }; bool blue = false; // error: 'blue' redefinition 

      However, in C ++ 11, scoped enums can fix this problem. scoped enum are declared var enum class .

       enum class Color{ blue, green, yellow }; bool blue = false; // fine, no other `blue` in scope Color cc = blue; // error! no enumerator `blue` in this scope Color cc = Color::blue; // fine auto c = Color::blue; // fine 
    • scope enums more strongly typed. But unscoped enums implicitly converted to other types

       enum Color{ blue, green, yellow }; std::vector<std::size_t> getVector(std::size_t x); Color c = blue; if (c < 10.1) { // compare Color with double !! auto vec = getVector(c); // could be fine !! } 

      However, in this case, scoped enums will be invalid.

       enum class Color{ blue, green, yellow }; std::vector<std::size_t> getVector(std::size_t x); Color c = Color::blue; if (c < 10.1) { // error ! auto vec = getVector(c); // error !! } 

      static_cast it via static_cast

       if (static_cast<double>(c) < 10.1) { auto vec = getVector(static_cast<std::size_t>(c)); } 
    • unscoped enums may be declared ahead.

       enum Color; // error!! enum class Color; // fine 
    • Both scoped and unscoped enums support the specification of the base type. The default type for scoped enums is int . unscoped enums do not have a base type by default.

  • Using the Concurrency API

    • Preference Based on Thread Based Tasks

      If you want to run the doAsyncWork function asynchronously, you have two main options. One of them is based on threads.

       int doAsyncWork(); std::thread t(doAsyncWork); 

      Another object is task based.

       auto fut = std::async(doAsyncWork); 

      Obviously, we can get the return value of doAsyncWork using a task easier than thread-based. With the task-based approach, it is easy, because the future returned from std::async offers a get function. The get function is even more important if doAsyncWork exception because get also provides access to this.

    • Thread-based calls for manual control of thread exhaustion, oversubscription, load balancing, and adaptation to new platforms. But task-based through std::async with a standard startup policy does not suffer from any of these drawbacks.

    Here are some links:

    Concurrency In C ++

    C / C ++ Programming Abstractions for Parallelism and Concurrency

+3
Jul 08 '15 at 0:58
source share

Keyword Override

Mark virtual functions in derived classes as overridden (if they really override, of course). This may protect against future errors, for example. by changing the signature of the virtual function in the base class and forgetting to change the signature in all derived classes, respectively.

+1
Nov 08 '16 at 9:26
source share



All Articles