Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring with C++ 11

Given the new toolset provided by c++ lots of programmers, aiming at code simplification, expressiveness, efficiency, skim through their old code and make tweaks (some pointless, some successful) to achieve their goals. Whilst trying not to loose too much time on such labors and just make non intrusive and self contained changes, what are the best practices?

Let me cross out the obvious :

  • Use auto to run 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 tie for multiple assignments that just produce C-style rows of code ( how to assign multiple values into a struct 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 just declare it as "final" and delete the code that achieved such a behavior http://www.parashift.com/c++-faq/final-classes.html

  • Use the delete keyword to explicitly hide constructors/destructors instead of declaring them private (eg code to create heap based objects, non copyable objects etc)

  • Turn trivial functors created just to facillitate the execution of a single STL algorithm into lambda functions (apart from reducing code cluttering you'll have guaranteed inlined calls)

  • Simplify RAII wrapping of an object by just using a smart pointer

  • Get rid of bind1st, bind2nd and just use bind

  • Replace hand written code for type traits (Is_ptr_but_dont_call_for_const_ptrs<> and such :) ) with standard code provided by < type_traits >

  • Stop including boost headers for functionallity now implented in STL (BOOST_STATIC_ASSERT vs static_assert)

  • Provide move semantics to classes (although this wouldn't qualify as a dirty/quick/easy change)

  • Use nullptr where possible instead of the NULL macro and get rid of the code that filled containers of pointers with 0's casted to object type

    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 the vector data accessing syntax

    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 (apart from avoiding the deprecated exception specifiation you get some speed benefits 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 code where you'd push a tempory in a container and hoped that the optimizer would ellide the copy away, with an "emplace" function where available, in order to perfectly forward the argument and construct directly an object into a container without temporary at all.

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

UPDATE

The answer by Shafik Yaghmour was rightfully awarded the bounty for having the greatest acceptance by the audience.

The answer by R Sahu was my accepted one, because the combination of features it proposes captures the spirit of refactoring : making code clearer and cleaner and simpler and elegant.

like image 200
Nikos Athanasiou Avatar asked Feb 02 '14 19:02

Nikos Athanasiou


People also ask

What is refactoring in C?

In computer programming and software design, code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior.

Is refactoring the same as removing bugs?

Code refactoring is not just a bug-removal process. Its main purpose is to improve readability and systematically boost application code performance. And even though the primary intention of code refactoring isn't to reduce or eliminate bugs, it does help prevent bugs in the long run.

When should you not refactor?

General logic based on this: If points 1-5 are all true, don't refactor. If any of points 2, 3, or 5 are false for multiple reasons (for example, multiple bugs would be fixed or multiple features would be easier to implement), count them as false once for each reason they are false.


2 Answers

1. Replacing rand

One of the big gains in C++11 has to be replacing the use of rand() with all the options available in the random header. Replacing rand() in many cases should be straight forward.

Stephan T. Lavavej probably made this point the strongest with his presentation 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 should be moving from std::random_shuffle to std::shuffle which comes out of the effort to Deprecate rand and Friends. This was recently covered in the SO question Why are std::shuffle methods being deprecated in C++14?.

Note that the distributions are not guaranteed to be consistent across platforms.

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

C++11 provides std::to_string which can be used to convert numerics to std::string it would produce the content as the equivalent std::sprintf. Most likely this would be used in place of either std::ostringstream or snprintf. This is more of a convenience, there is probably not much of performance difference and we can see from the Fast integer to string conversion in C++ article there are probably much faster alternatives available if performance is the main concern:

#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 in place of template meta-programming

If you are dealing with literals there may be cases where using constexpr functions over template meta-programming may produce code that is more clear and possibly compiles faster. The article Want speed? Use constexpr meta-programming! provides an example of prime number determination using template meta-programming:

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 performs much better than the template meta-programming implementation.

4. Using class member initialization to provide default values

As was recently covered in Has the new C++11 member initialization feature at declaration made initialization lists obsolete? class member initialization can be used to provide default values and can simplify cases where a class has multiple constructors.

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

This saves a bit of typing, but the real benefits come in classes with multiple constructors. Often, all constructors use a common initializer for a member:

and provides an example of members which have 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 a single default is lost in the mess of code and could easily become a problem during maintenance. Instead, we can factor out the initialization of the 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 using in class member initializers is no longer an aggregate although this restriction is removed in C++14.

5. Use fixed width integer types from cstdint instead of hand rolled typedefs

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

int8_t int16_t  int32_t  int64_t  intptr_t 

Although several of them an optional, for the exact width integer types the following from C99 section 7.18.1.1 applies:

These types are optional. However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation, it shall define the corresponding typedef names.

like image 153
Shafik Yaghmour Avatar answered Oct 07 '22 11:10

Shafik Yaghmour


I would add delegating constructors and in-class member initializers to the list.

Simplification By Using Delegating Constructors and In-Class Initialization

With 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_; }; 

With 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_; }; 
like image 36
R Sahu Avatar answered Oct 07 '22 11:10

R Sahu