إعادة بيع ديون مع C++ 11




c++11 refactoring (8)

وبالنظر إلى مجموعة الأدوات الجديدة المقدمة من قبل الكثير من المبرمجين ، نهدف إلى تبسيط الكود ، والتعبير ، والكفاءة ، والقراءة عبر الشفرة القديمة وإجراء التعديلات (بعضها بلا جدوى ، وبعضها ناجح) لتحقيق أهدافها. مع محاولة عدم إهدار الكثير من الوقت على مثل هذه الأعمال وإجراء تغييرات غير تطفلية وذاتية ، ما هي أفضل الممارسات؟

دعني أخرج ما هو واضح:

  • استخدم السيارات لتشغيل الحلقات القائمة على التكرار:

    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);
    
  • استخدام التعادل لمهام متعددة تقوم فقط بإنتاج صفوف من الكود على شكل حرف C ( كيفية تعيين قيم متعددة في بنية في وقت واحد؟ )

    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);
    
  • لجعل فئة غير قابلة للوراثة فقط أعلن أنها "نهائية" واحذف الرمز الذي حقق مثل هذا السلوك http://www.parashift.com/c++-faq/final-classes.html

  • استخدم الكلمة الرئيسية حذف لإخفاء صانعي / destructors بشكل واضح بدلاً من إعلانها الخاصة (على سبيل المثال ، رمز لإنشاء كائنات تستند إلى كومة الذاكرة المؤقتة ، كائنات غير قابلة للنسخ إلخ)

  • قم بإدارة المسابير البسيطة التي تم إنشاؤها فقط لتجميل تنفيذ خوارزمية STL واحدة في وظائف lambda (بصرف النظر عن تقليل تشوش التعليمات البرمجية ، سيكون لديك مكالمات مضمونة مضمونة)

  • بسط التفاف RAII لكائن بمجرد استخدام مؤشر ذكي

  • تخلص من bind1st ، bind2nd ومجرد استخدام الربط

  • استبدل الشفرة المكتوبة يدويًا لكتابة النوع (Is_ptr_but_dont_call_for_const_ptrs <> و :)) مع الكود القياسي المقدم من <type_traits>

  • التوقف عن تضمين رؤوس تعزيز للوظائف التي يتم زرعها الآن في STL (BOOST_STATIC_ASSERT vs static_assert)

  • توفير دلالات الانتقال إلى الفصول الدراسية (على الرغم من أن ذلك لن يتأهل كتغيير سيئ / سريع / سهل)

  • استخدام nullptr حيثما أمكن بدلاً من الماكرو NULL والتخلص من التعليمة البرمجية التي تملأ حاويات مؤشرات مع 0 s يلقي إلى نوع الكائن

    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);
    
  • قم بإلغاء تحديد بيانات المتجه التي تصل إلى صيغة الجملة

    std::vector<int> vec;
    &vec[0];    // access data as a C-style array
    vec.data(); // new way of saying the above
    
  • استبدل الرمي () مع noexcept (بصرف النظر عن تجنب تحديد الاستثناء المتوقف ، يمكنك الحصول على بعض مزايا السرعة 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
    
  • استبدل الرمز في المكان الذي تريد فيه دفع درجة مؤقت في الحاوية ويأمل أن يقوم المُحسِّن بإزاحة النسخة ، مع وظيفة "emplace" حيثما يكون ذلك متاحًا ، من أجل إعادة توجيه الحجة بشكل ملائم وإنشاء كائن في حاوية بدون مؤقت في الكل.

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

تحديث

لقد تم منح جواب شفيق يغمور جائزة فضل الحصول على القبول الأكبر من الجمهور.

كان الجواب من قبل R Sahu هو الذي قبلته ، لأن الجمع بين الميزات التي يقترحها يجسد روح إعادة البناء : جعل الشفرة أكثر وضوحًا ونظافة وأبسط وأنيقة.


  1. تغيير std::map إلى std::unordered_map و std::set إلى std::unordered_set حيث يكون ترتيب عناصر الحاوية غير ذي صلة ، يعزز الأداء بشكل ملحوظ.
  2. باستخدام std::map::at بدلاً من استخدام إدراج بنية قوس مربع ، عندما تريد تجنب الإدراج غير الطوعي.
  3. استخدم قوالب الاسم المستعار عندما تريد قوالب typedef .
  4. استخدام قوائم التهيئة بدلاً من الحلقات لتهيئة حاويات STL.
  5. استبدل صفائف C ذات الحجم الثابت بـ std :: array.

  1. فضل enums scoped إلى enums unscoped

    • في الترجمات C ++ 98 ، لا يوجد أي نطاق خاص بالتعدادات مثل مقتطف الشفرة التالي. تنتمي أسماء هؤلاء العدادين إلى النطاق الذي يحتوي على التعداد ، أي شيء آخر في هذا النطاق قد يكون له نفس الاسم.

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

      ومع ذلك ، في C ++ 11 ، يمكن scoped enums حل هذه المشكلة. تم إعلان scoped enum 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 من scope enums بقوة أكبر. ولكن ، فإن unscoped enums من التعداد غير unscoped enums تحول ضمنيًا إلى أنواع أخرى

      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 !!
      }
      

      ومع ذلك ، سيتم فشل scoped enums في هذه الحالة.

      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

      if (static_cast<double>(c) < 10.1) {
         auto vec = getVector(static_cast<std::size_t>(c));
      } 
      
    • قد يتم الإعلان عن unscoped enums .

      enum Color;          // error!!
      enum class Color;    // fine
      
    • unscoped كل من unscoped scoped unscoped وغير unscoped مواصفات النوع الأساسي. النوع الأساسي الافتراضي scoped enums هو int . لا يوجد في Unscoped enums نوع أساسي افتراضي.

  2. باستخدام واجهة برمجة تطبيقات التزامن

    • تفضل القائمة على أساس الموضوع

      إذا كنت ترغب في تشغيل وظيفة doAsyncWork بشكل غير متزامن ، doAsyncWork خياران أساسيان. واحد هو القائم على الخيط

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

      والآخر هو القائم على المهمة .

      auto fut = std::async(doAsyncWork);
      

      من الواضح ، يمكننا الحصول على قيمة الإرجاع من doAsyncWork من خلال المهام القائمة بسهولة أكثر من مؤشر الترابط . مع النهج task-based ، الأمر سهل ، لأن المستقبل الذي يتم إرجاعه من std::async يوفر وظيفة get. تعد وظيفة get أكثر أهمية إذا قامت doAsyncWork بإرسال استثناء ، لأن get على إمكانية الوصول إلى ذلك أيضًا.

    • استدعاءات Thread-based إلى خيوط المعالجة لإدارة يدوية لاستنفاد ترابط ، وتغطية الاكتتاب ، وموازنة الحمل ، والتكيف مع المنصات الجديدة. ولكن Task-based std::async مع نهج التشغيل الافتراضي من عدم وجود أي من هذه السلبيات.

    فيما يلي عدة روابط:

    التزامن في C ++

    تجريدات برمجة C / C ++ للتوازي والتوافق


استخدم صيغة التهيئة الموحدة للتهيئة المتغيرة

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

لتجنب المشاكل مثل تحليل c ++ الأكثر إثارة للارتباك (ما تبقى من الأسباب التي تجعل الطريقة الجديدة متفوقة يتم شرحها في المقالة المرتبطة بواسطة Herb Sutter)


استخدم المؤشرات الذكية. لاحظ أنه لا يزال هناك سبب وجيه لوجود مؤشرات عارية في بعض الحالات ، فإن أفضل طريقة للتحقق من أن يكون المؤشر ذكيًا هو البحث عن استخدامات delete عليه.

يجب أن يكون هناك أي سبب لاستخدام new سواء. استبدل كل new بـ make_shared أو make_unique .

لسوء الحظ ، لم تكن make_unique في معيار C ++ 11 ، أفضل حل IMO هو تطبيقه بنفسك ( انظر الرابط السابق ) ، ووضع بعض وحدات الماكرو للتحقق من إصدار make_unique ( make_unique متوفر في C ++ 14) .

يعد استخدام make_unique و make_shared أمرًا مهمًا حقًا لجعل استثناء التعليمات البرمجية الخاص بك آمنًا.


قم بتحسين الوظائف الحسابية البسيطة مع constexpr ، خاصة إذا كانت تسمى داخل الحلقات الداخلية. هذا من شأنه أن يسمح لل مترجم بحسابها عند التحويل البرمجي مما يوفر لك الوقت

مثال

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

مثال آخر هو استخدام std::enable_if لتحديد أنواع معلمات القالب المسموح بها في وظيفة / فئة قالب معين. سيؤدي ذلك إلى جعل الشفرة أكثر أمانًا (في حالة عدم استخدام SFINAE لربط وسيطات القالب المحتملة في شفرتك القديمة) عندما تفترض ضمنيًا بعض الخصائص عن أنواع القوالب وأنها مجرد سطر إضافي واحد من الكود

مثال:

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
}

تحديث 1: إذا كان لديك صفيف صغير حيث يتم معرفة الحجم في وقت التحويل البرمجي وكنت ترغب في تجنب الحمل من التخصيص في std :: vector (بمعنى: تريد الصفيف في بنية تخزين العناصر) ، يمكنك اختيار فقط في كان C ++ 03 يستخدم صفيف c-style. قم بتغيير ذلك إلى std::array . إنه تغيير بسيط يوفر لك الكثير من الوظائف الموجودة في تخصيص std :: vector + stack (أسرع بكثير من تخصيص الكومة كما قلت من قبل).


لكل بناء جملة:

std::vector<int> container;

for (auto const & i : container)
  std::cout << i << std::endl;

وأود أن أضيف المفوضين المفوضين والمبدلين الأعضاء في الفئة إلى القائمة.

التبسيط باستخدام مندوبين مفوضين وتهيئة في الفئة

مع 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_;
};

1. استبدال راند

يجب أن يكون أحد المكاسب الكبيرة في C ++ 11 هو استبدال استخدام rand() بكافة الخيارات المتاحة في العنوان العشوائي . استبدال rand() في كثير من الحالات يجب أن يكون بشكل مستقيم للأمام.

ربما جعل Stephan T. Lavavej هذه النقطة الأقوى مع عرضه التقديمي rand () الذي يعتبر ضارًا . تعرض الأمثلة توزيع صحيح موحد من [0,10] باستخدام 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 ;
}

واستخدام 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 ;
}

جنبا إلى جنب مع هذا ينبغي أن ينتقل من std::random_shuffle إلى std::random_shuffle الذي يخرج من الجهد لإهمال الراند والأصدقاء . تم تناول هذا مؤخرًا في السؤال المتعلق بـ SO: لماذا يتم إيقاف طرق shuffle في C ++ 14؟ .

لاحظ أن التوزيعات ليست مضمونة لتكون متسقة عبر المنصات .

2. باستخدام std :: to_string بدلاً من std :: ostringstream أو sprintf

يوفر C ++ 11 std::to_string والذي يمكن استخدامه لتحويل الأعداد إلى std::string سوف ينتج عن المحتوى كـ std::sprintf المكافئ. على الأرجح سيتم استخدام هذا بدلاً من std::ostringstream أو snprintf . هذا أكثر راحة ، ربما لا يوجد فرق كبير في الأداء ويمكننا أن نرى من العدد الصحيح السريع لتحويل السلسلة في مقالة C ++ هناك على الأرجح بدائل أسرع بكثير إذا كان الأداء هو الشاغل الرئيسي:

#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. استخدام constexpr بدلا من البرمجة الفوقية القالب

إذا كنت تتعامل مع القيم الحرفية ، فقد تكون هناك حالات حيث قد يؤدي استخدام وظائف constexpr على البرمجة الفوقية للقوالب إلى إنشاء كود أكثر وضوحًا وقد يتم تجميعه بشكل أسرع. المادة تريد السرعة؟ استخدام البرمجة الفوقية constexpr! يوفر مثالاً على تحديد العدد الأولي باستخدام البرمجة التلوية القالب:

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

واستخدام وظائف constexpr:

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);
}

إصدار constexpr أقصر وأسهل في الفهم ويبدو أنه أفضل بكثير من تنفيذ برمجة meta النموذجية.

4. استخدام تهيئة عضو الفئة لتوفير القيم الافتراضية

كما تم تغطيته مؤخرًا هل انتهت ميزة التهيئة الجديدة لعضو C ++ 11 في إعلان قوائم التهيئة القديمة؟ يمكن استخدام تهييء أعضاء الفئة لتوفير القيم الافتراضية ويمكن تبسيط الحالات التي يكون فيها للفئة منشئات متعددة.

يقدم Bjarne Stroustrup مثالًا جيدًا في الأسئلة الشائعة في C ++ 11 ، حيث يقول:

هذا يوفر قليلاً من الكتابة ، ولكن الفوائد الحقيقية تأتي في فصول مع العديد من المنشئات. في كثير من الأحيان ، تستخدم جميع المنشئين مُهيئًا مشتركًا لأحد الأعضاء:

ويوفر مثالاً للأعضاء الذين لديهم مُبدئ شائع:

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

ويقول:

يتم فقدان حقيقة أن hash_algorithm و s افتراضي واحد في الفوضى من التعليمات البرمجية ويمكن بسهولة أن تصبح مشكلة أثناء الصيانة. بدلاً من ذلك ، يمكن أن نقوم بمعالجة التهيئة لأعضاء البيانات:

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

لاحظ أنه في C ++ 11 ، لم يعد استخدام فئة في مُهيئات أعضاء الفئة تجميعًا على الرغم من إزالة هذا القيد في C ++ 14.

5. استخدم أنواع الأعداد الصحيحة ذات العرض الثابت من cstdint بدلاً من typedefs المدرفلة يدوياً

بما أن معيار C ++ 11 يستخدم C99 كمرجع معياري ، فإننا نحصل على أنواع الأعداد الصحيحة ذات العرض الثابت ، كذلك. فمثلا:

int8_t
int16_t 
int32_t 
int64_t 
intptr_t

على الرغم من أن العديد منها اختياري ، بالنسبة لأنواع الأعداد الصحيحة للعرض بالضبط ، ينطبق ما يلي من القسم C99 7.18.1.1 :

هذه الأنواع اختيارية. ومع ذلك ، إذا كان التنفيذ يوفر أنواعًا صحيحة بعرض 8 أو 16 أو 32 أو 64 بت ، فلا بتات حشو و (بالنسبة للأنواع الموقعة) التي لها تمثيل مكمل للاثنين ، يجب أن تحدد أسماء الرموز المميزة المطابقة.





refactoring