Betapa mudahnya untuk memodernisasi kode C ++

Halo, Habr!



Kami menyampaikan kepada Anda terjemahan dari artikel praktis singkat tentang menangani warisan yang berlebihan dalam kode C ++. Kami berharap ini akan menarik.



Baru-baru ini, komunitas C ++ secara aktif mempromosikan penggunaan standar baru dan modernisasi basis kode yang ada. Namun, bahkan sebelum standar C ++ 11 dirilis, pakar C ++ terkenal seperti Andre Alexandrescu, Scott Myers, dan Herb Sutter mempromosikan pemrograman C ++ generik, yang memenuhi syarat sebagai "desain C ++ modern". André Alexandrescu mengatakannya seperti ini:



Desain C ++ modern mendefinisikan dan secara sistematis menggunakan komponen generik - artefak desain yang sangat fleksibel yang dapat dicampur dan dicocokkan untuk menghasilkan perilaku yang kaya dalam potongan kode ortogonal kecil.


Tiga pernyataan menarik dalam tesis ini:



  • Desain C ++ modern mendefinisikan dan secara sistematis menggunakan komponen generik .
  • Desain yang sangat fleksibel .
  • Dapatkan perilaku kaya dengan sepotong kode ortogonal kecil .


Peningkatan kode yang ditulis dalam C ++ tidak terbatas pada pengenalan standar baru, tetapi juga melibatkan penggunaan praktik terbaik yang berlaku untuk bahasa pemrograman apa pun dan membantu meningkatkan basis kode. Pertama, mari kita bahas beberapa langkah sederhana untuk meningkatkan basis kode Anda secara manual. Di bagian ketiga, kita akan berbicara tentang peningkatan kode otomatis.



Meningkatkan kode sumber secara manual



Mari kita ambil algoritme sebagai contoh dan coba untuk memodernisasikannya. Algoritma digunakan untuk perhitungan, pemrosesan data, dan penarikan kesimpulan otomatis. Memprogram algoritme terkadang merupakan tugas yang tidak sepele dan bergantung pada kompleksitasnya. Dalam C ++, upaya signifikan dilakukan untuk menyederhanakan implementasi dan meningkatkan kekuatan algoritme.

Mari kita coba memodernisasi implementasi algoritma quicksort ini:



//  
int partition(int* input,int p,int r){
        int pivot = input[r];
        while( p < r ){
                 while( input[p]< pivot )
                     p++;
                 while( input[r]> pivot )
                    r--;
                if( input[p]== input[r])
                    p++;
                elseif( p < r ){
                     int tmp = input[p];
                     input[p]= input[r];
                     input[r]= tmp;
                }
        }
         return r;
}
//    
void quicksort(int* input,int p,int r){
        if( p < r ){
              int j = partition(input, p, r);        
              quicksort(input, p, j-1);
              quicksort(input, j+1, r);
        }
}

      
      





Bagaimanapun, semua algoritme memiliki beberapa kesamaan:



  • Menggunakan wadah untuk elemen jenis tertentu dan mengulanginya.
  • Perbandingan elemen
  • Beberapa operasi pada elemen


Dalam implementasi kami, container adalah array mentah dari integer, dan kami mengulangi operasi increment dan decrement satu per satu. Perbandingan dilakukan dengan menggunakan “<”



dan “>”



, dan kami juga melakukan beberapa operasi pada data, misalnya, kami menukarnya.



Mari kita coba meningkatkan masing-masing fitur algoritme ini:



Langkah 1: Mengubah Container menjadi Iterator



Jika kita meninggalkan wadah umum, maka kita akan dipaksa untuk hanya menggunakan elemen dari tipe tertentu. Untuk menerapkan algoritme yang sama ke jenis lain, kita harus menyalin dan menempelkan kode. Kontainer generik mengatasi masalah ini dan memungkinkan Anda menggunakan elemen apa pun. Misalnya, dalam algoritme pengurutan cepat, Anda dapat menggunakannya std::vector<T>



sebagai wadah alih-alih larik mentah.



Array mentah atau std::vector



hanya salah satu dari berbagai opsi untuk mewakili banyak elemen. Algoritme yang sama berlaku untuk daftar tertaut, antrean, atau penampung lainnya. Saat bekerja dengan iterator, yang terbaik adalah mengabstraksi wadah yang digunakan.



Iterator adalah objek apa pun yang, menunjuk ke elemen dalam rentang tertentu, dapat melakukan iterasi pada semua elemen dalam rentang yang diberikan menggunakan sekumpulan operator (yang mencakup, setidaknya, kenaikan oleh satu operator (++) dan operator dereferensi (*)). Iterator terbagi dalam lima kategori berdasarkan fungsi yang mereka lakukan: Input, Output, Iterator satu arah, Iterator dua arah, dan akses acak.



Dalam algoritme kami, kami harus menentukan iterator mana yang akan kami gunakan. Untuk melakukan ini, kita perlu mengidentifikasi iterasi mana yang kita gunakan. Algoritme quicksort menggunakan iterasi kenaikan per satu dan penurunan satu. Oleh karena itu, kita membutuhkan iterator dua arah. Dengan iterator, Anda dapat menentukan metode seperti ini:



template< typename BidirectionalIterator >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last )
      
      





Langkah 2: menggeneralisasi pembanding, jika memungkinkan



Beberapa algoritme harus memproses tidak hanya angka, tetapi, misalnya, string atau kelas. Dalam hal ini, Anda perlu membuat komparator digeneralisasikan; ini akan memungkinkan kita mencapai generalisasi yang lebih besar dari keseluruhan algoritme.



Algoritme pengurutan cepat juga dapat diterapkan ke daftar string. Karenanya, komparator umum lebih cocok untuk kita.



Dengan menggunakan komparator umum, Anda dapat mengubah definisi seperti ini:



template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp )
      
      





Tahap 3: Ganti operasi yang ada dengan yang standar



Sebagian besar algoritme menggunakan operasi berulang seperti min



, max



dan swap



. Saat melakukan operasi seperti itu, lebih baik tidak menemukan kembali roda dan menggunakan implementasi standar yang ada di header <algorithm>



.



Dalam kasus kami, kami dapat menggunakan metode swap dari perpustakaan standar STL daripada membuat metode kami sendiri.



std::iter_swap( pivot, left );
      
      





Dan inilah hasil modifikasi setelah tiga langkah ini:



#include <functional>
#include <algorithm>
#include <iterator>
 
template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp ) {
    if( first != last ) {
        BidirectionalIterator left  = first;
        BidirectionalIterator right = last;
        BidirectionalIterator pivot = left++;
 
        while( left != right ) {
            if( cmp( *left, *pivot ) ) {
                ++left;
            } else {
                while( (left != right) && cmp( *pivot, *right ) )
                    --right;
                std::iter_swap( left, right );
            }
        }
 
        --left;
        std::iter_swap( pivot, left );
 
        quick_sort( first, left, cmp );
        quick_sort( right, last, cmp );
    }
}
 
template< typename BidirectionalIterator >
    inline void quick_sort( BidirectionalIterator first, BidirectionalIterator last ) {
        quick_sort( first, last,
                std::less_equal< typename std::iterator_traits< BidirectionalIterator >::value_type >()
                );
    }
      
      





Implementasi ini memiliki keuntungan sebagai berikut:



  • Berlaku untuk semua jenis elemen.
  • Penampung dapat berupa vektor, kumpulan, daftar, atau lainnya yang disediakan dengan iterator dua arah.
  • Implementasi ini menggunakan fungsi standar yang dioptimalkan dan diuji.


Upgrade otomatis



Sangat menarik untuk secara otomatis mengidentifikasi tempat-tempat di mana Anda dapat menggunakan fitur-fitur tertentu dari C ++ 11 / C ++ 14 / C ++ 17 dan, jika kondisinya memungkinkan, secara otomatis mengubah kodenya. Untuk tujuan seperti itu, ada alat clang-tidy berfitur lengkap yang digunakan untuk secara otomatis mengubah kode C ++ yang ditulis sesuai dengan standar lama. Setelah transformasi ini, kode menggunakan fitur dari standar yang lebih baru jika sesuai.



Berikut adalah beberapa area di mana clang-tidy menyarankan peningkatan kode:



  • Mengganti: Temukan tempat di mana Anda dapat menambahkan penunjuk pengganti untuk fungsi instance yang menggantikan fungsi virtual di kelas dasar tanpa memilikinya
  • : for(…; …; …)



    , , , .
  • : const-ref



    , .
  • auto_ptr



    : std::auto_ptr



    std::unique_ptr



    .
  • -: , auto



    .
  • nullptr



    : , nullptr



    , .
  • std::bind



    : std::bind , , . , , .
  • : C C++ . C++. C++ 14 [depr.c.headers].
  • std::shared_ptr



    : std::shared_ptr



    new



    , std::make_shared



    .
  • std::unique_ptr



    : std::shared_ptr



    new



    , std::make_unique



    , C++14.
  • : , , .


Pengembang yang telah menguasai Clang dapat dengan mudah belajar menggunakan alat clang-tidy. Tetapi saat bekerja dengan Visual C ++, serta kompiler lain, Anda dapat menggunakan CppDepend , yang menyertakan clang -tidy.



All Articles