تطبيق مكافحة ناقلات الحديثة

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

vector.h:

#ifndef VECTOR_H
#define VECTOR_H

template
class Vector
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;

    Vector();
    Vector(unsigned int size);
    Vector(unsigned int size, const T& initial);
    Vector(const Vector& v);
    Vector(Vector && v);
    ~Vector();

    Vector& operator =(Vector const& v);
    Vector& operator =(Vector && v);//move assignment

    inline unsigned int capacity() const { return m_capacity; }
    inline unsigned int size() const { return m_size; }
    inline bool empty() const { return m_size == 0; }
    inline iterator begin() { return buff; }
    inline iterator end() { return buff + (m_size - 1) * sizeof(T); }
    inline const_iterator c_begin() { return buff; }
    inline const_iterator c_end() { return buff + (m_size - 1) * sizeof(T); }
    T& front();
    T& back();
    void pushBack(const T& val);
    void popBack();

    void reserve(unsigned int capacity);
    void resize(unsigned int size);

    T& operator [](unsigned int index);
    void clear();

private:
    unsigned int m_capacity;
    unsigned int m_size;
    unsigned int Log;
    T* buff;

    void swap(Vector & v);
};

#endif

vector.cpp:

#include "vector.h"
#include  //for Log

template
Vector::Vector() : 
    m_capacity(0),
    m_size(0),
    Log(0),
    buff(nullptr) {}

template
Vector::Vector(unsigned int size) : //an argument of 0 leads to a capacity value of 1, distinct from the default ctor
    m_size(size), 
    Log(ceil(log((double)size)/log(2.0))), 
    m_capacity(1 << Log), 
    buff(size ? new T[m_capacity] : nullptr) {}

template
Vector::Vector(unsigned int size, const T& initial) :
    m_size(size), 
    Log(ceil(log((double)size)/log(2.0))), 
    m_capacity(1 << Log), 
    buff(size ? new T[m_capacity] : nullptr)
{
    for (unsigned int i = 0; i < size; i++)
    {
        //using placement new to place each element of v[i] in our already allocated buffer
        new(buffer + i) T(initial);
    }
}

template
Vector::Vector(Vector const& v) :
    m_capacity(v.capacity()), 
    m_size(v.size()), 
    Log(v.Log),  
    buff(m_size ? new T[m_capacity] : nullptr)
{
    std::copy(v.buff, v.buff + v.m_size, buff); //std::copy is better than loops if we have both arrays
    //for (unsigned int i = 0; i < m_size; i++)
        //new(buffer + sizeof(T)*i) T(v[i]);
}

template
Vector::Vector(Vector && v)
    : m_size(0),
    m_capacity(0),
    buff(nullptr),
    Log(0)
{
    swap(v);
}

template
Vector& Vector::operator =(Vector const& v)
// //not strong exception, but will write a wrapper that makes it strong exception; lowest level -> fastest
{
    delete[] buff;
    buff = nullptr;
    buff = v.m_capacity ? new T[v.m_capacity * sizeof(T)] : nullptr;
    m_size = v.m_size;
    m_capacity = v.m_capacity;
    Log = v.Log;
    std::copy(v.buff, v.buff + m_size-1, buff);
    return *this;
}

template
Vector& Vector::operator =(Vector && v)
{
    delete[] buff; //prep this
    m_size = 0;
    buff = nullptr;
    m_capacity = 0;
    Log = 0;

    swap(v);

    return *this;
}

template
Vector::~Vector()
{
    delete[] buff;
    buff = nullptr;
    m_size = 0;
    m_capacity = 0;
    Log = 0;
}

template
T& Vector::operator[](unsigned int index)
{
    return buff[index];
}

template
T& Vector::front()
{
    return buff[0];
}

template
T& Vector::back()
{
    return buff[m_size - 1];
}

template
void Vector::reserve(unsigned int capac)
{
    T* newbuff = new T[capac];

    for (unsigned int i = 0; i < m_size; i++)
        newbuff[i] = buff[i];

    m_capacity = capac;
    delete[] buff;
    buff = newbuff;
}

template
void Vector::resize(unsigned int size)
{
    Log = ceil(log((double)size)/log(2.0));
    reserve(1 << Log);
    m_size = size;
}

template
void Vector::pushBack(const T& val)
{
    if (m_size >= m_capacity)
    {
        reserve(1 << Log++);
    }

    buff[m_size++] = val;
}

template
void Vector::popBack()
{
    (reinterpret_cast(buff)[m_size-- - 1]).~T();
}

template
void Vector::clear()
{
    for (int i = 0; i < m_size; i++)
        (reinterpret_cast(buff)[i]).~T();

    m_size = 0;
    m_capacity = 0;
    Log = 0;
    buff = 0;
}

template
void Vector::swap(Vector & v)
{
    std::swap(m_size, v.m_size);
    std::swap(m_capacity, v.m_capacity);
    std::swap(buff, v.buff);
    std::swap(Log, v.Log);
}
12
هل لديك قراءة من المقالات الأربعة كتبت حول إنشاء ناقل في C ++: Loki Astari Vector الكثير من النصائح ل مساعدة.
وأضاف المؤلف Joseph Daigle, مصدر
vector إعادة اختراع العجلة أمر شائع جدًا. راجع هنا للحصول على مجموعة من نتائج البحث.
وأضاف المؤلف Trent Turner, مصدر
إذا كان المقصود أن يكون إعادة اختراع العجلة ، يجب عليك إضافة تلك العلامة (والنظر في باستخدام أسماء مطابقة مثل push_back و cbegin ).
وأضاف المؤلف Toby Speight, مصدر
لماذا تعتقد أن لغة النسخة والمبادلة قد تقلل السرعة؟
وأضاف المؤلف Toby Speight, مصدر
هل هناك سبب لعدم استخدام std :: vector ؟
وأضاف المؤلف Edward, مصدر
لا أعتقد أن الشفرة التي تم نشرها كانت جاهزة فعليًا للمراجعة. يرجى اختبار شفرتك قبل إرسالها هنا.
وأضاف المؤلف HappyCoding, مصدر
مضمنة ككلمة رئيسية للدوال المعرفة في الصف غير مجدية لأن هذه الدالات تحصل على مضمنة آليًا.
وأضاف المؤلف Pythonaddict, مصدر

6 إجابة

إدارة الذاكرة

  • new T[m_capacity] default-constructs m_capacity objects of type T in there.

    This might hurt أداء a lot if Ts default constructor is not trivial, and even then it still isn't necessary.

    Even worse, in pushBack a new T object gets copy constructed in the place of the already existing one (if m_size < m_capacity), without properly deleting it. This might cause an abundance of errors, especially if Ts destructor is not trivial (e.g. it would leak memory).

    Consider using std::aligned_storage for storing the objects instead.

  • The implementation leaks memory if an exception is thrown during any of the member functions which call new (e.g. due to no being able to allocate new memory). Using std::unique_ptr helps preventing those leaks.

  • clear leaks memory (no call to delete[]).

  • Some member functions (see below) set m_capacity, Log and/or m_size to unexpected values, which in turn can cause wrong allocations and/or out of bounds memory accesses in other places.

    • reserve accesses out of bounds memory if capac < m_size due to newbuff[i] being out of bounds.

    • For any given m_capacity, the first call to pushBack where m_size >= m_capacity doesn't allocate bigger space (Log gets increased after the call to reserve in reserve(1 << Log++)).

      This means Log is out of sync from m_capacity, buff[m_size++] is out of bounds and m_size > m_capacity after the call.

    • The constructors taking a size parameter initialize m_capacity before Log (due to member definition order inside the class), but refer to the uninitialized value of Log for doing so. This sets m_capacity to a wrong value, and thus allocates a wrong amount of memory for buff (if size > 0).

  • pushBack has a potential use-after-free error: If val refers to an element inside this Vector and Vector needs to reallocate, val will dangle when the new object gets copy constructed.

  • The copy assignment operator deletes buff without checking whether this == &v. If that condition is true, it deleted v.buff (which it then will try to index in order to populate the newly allocated buff).

    • Also, that newly allocated buff is too large by a factor of sizeof(T).

You might want to look into using std::unique_ptr for buff. While this doesn't fix out of bounds memory access, it will help with some of the حالات أخرى.

الاستخدام مع الخوارزميات القياسية

  • The name changes of push_back, pop_back, cbegin and cend make this container unusable for standard algorithms.

  • begin and end should provide a const overload, so they can be called on a const Vector&.

  • Similarly, cbegin and cend should be declared const.

  • Also, push_front, pop_front, emplace, emplace_back, emplace_front, remove, insert and all the reverse iterator variants are missing.

تسمية

  • نمط الأسماء للمتغيرات الخاصة بالأعضاء غير متناسق: m_size / m_capacity و buff و Log . اختر واحدًا وابقَ ثابتًا!

تصميم فئة

  • There is no way to handle types that have no default or no copy constructor. Please provide an overload on push_back(T&&) to accept non-copyable types, and emplace_front/emplace_back methods to construct T objects for types that have neither a copy nor a move constructor. (The emplace-type methods can also provide أداء benefits by reducing copy/move constructions.)

  • A const Vector& has no way to access elements inside it. Please provide const overloads for front, back and operator[].

  • Vector::swap should likely be public.

  • Vector isn't copyable unless T itself is. Thus, the copy constructor and copy assignment operator should be deleted for those cases.

  • Why force the buffer to a size that is a power of 2? If I specify an exact size in the constructor (or via reserve/resize), I'd expect the Vector to have exactly that size (and not waste additional memory).

  • No member functions give any exception specification. This prevents certain kinds of optimization. Especially the move constructor, move assignment operator and destructor should be noexcept.

حالات أخرى

  • Why reinterpret_cast(buff)? buff is already of type T*.

  • inline actually doesn't do anything in this implementation.

  • clear() doesn't call destructors for objects between m_size and m_capacity. (Then again, it doesn't delete[] buff, which would fix that.)

  • Prefer {} over () for initialization. While there is no instance of the most vexing parse in this code, it always helps to be mindful.

  • As @TobySpeigh mentions, please put all code into the header file, as all code for templates needs to be accessible where instantiated. (Currently, one would have to use #include "vector.cpp", which is surprising.)

  • Iterators returned by end() and cend() are expected to point one element after the last one (i.e. buff + m_size). Currently, they point wildly out of bounds (buff + (m_size - 1) * sizeof(T)).

  • Sometimes std::copy is used for copying, sometimes a raw for loop. This is inconsistent.

    • In some of those cases, they could be replaced by a range-based std::move (if T is nothrow movable).

    • In some cases, the last element isn't copied due to bound miscalculations.

أداء

  • No member function gives any exception specification. This might impede أداء.

  • Why use floating point math for Log? (And why use Log at all?)

  • Try to std::move objects if possible. This can be asserted with template meta programming.

  • You might consider using realloc for reallocations if T is trivially copyable.

  • And as always, if أداء is important, measure it! Doing premature optimization (especially at the cost of readability or extensibility) is bad.

    Also, be sure your code is working correctly before trying to optimize. It doesn't matter if it is really fast if it produces the wrong result!

20
وأضاف
hoffmale نعم ، هذا صحيح. في الأساس ، يتلخص الأمر ، حتى إذا كنت أعرف أن A و B بجوار next-new'd بجوار بعضهما البعض في الذاكرة ، (& A) +1 ليست طريقة قانونية للحصول على < code> B . ولكن إذا كانت عناصر مباشرة في مصفوفة ، فهي كذلك. ولا يمكنك تغيير حجم المصفوفات في C ++ ، فقط قم بإنشاء مجموعات جديدة من أحجام معينة.
وأضاف المؤلف HamZa, مصدر
هوف ، قد يكون من الجدير بالذكر أنه لا توجد طريقة محمولة قياسية متوافقة لتنفيذ المتجهات بالكامل بسبب "الأخطاء" في كيفية تعريف القياسي للذاكرة المتجاورة والمصفوفات والمشكلات.
وأضاف المؤلف HamZa, مصدر
Davislor Power من توزيعين كومة تقود إلى مشكلة ناقلات المشي إذا كان لديك ناقل واحد متزايد ؛ لا يوجد مقدار من المخازن المؤقتة المتجهة تم تحريرها مسبقاً يمكن احتواء طلب الذاكرة الجديد الخاص بك. يمكن لأي طاقة فرعية من 2 تجنب ذلك ، حيث يمكن استخدام وحدات التخزين K السابقة التي تم إلغاء تخصيصها للنمو K + الثاني بعد بعض التكرارات K.
وأضاف المؤلف HamZa, مصدر
Davislor: يوفر كل عامل نمو ثابت \ $ \ mathcal {O} (\ log n) \ $ reallocations عند الإدراج. والتقليل من تجزئة الرأس قد يكون هدفا عاما جيدا ، ولكن لا ينبغي أن تضيع الذاكرة إذا كان المستخدم يحدد صراحة متطلبات حجمها أعلى IMHO.
وأضاف المؤلف HappyCoding, مصدر
Davislor: الجزء المتعلق "بتقليل استخدام الذاكرة" كان فقط للحالة التي يحدد فيها المستخدم صراحةً متطلبات حجمها (أي يحدد حجمًا في المُنشئ أو المكالمات تغيير الحجم / احتياطي </كود>). في الحالة العامة ، استخدام عامل نمو وبعض الارشادي للحجم على ما يرام.
وأضاف المؤلف HappyCoding, مصدر
Yakk: يمكن أن تعطي إعطاء بعض مزيد من المعلومات حول ذلك؟ أود معرفة المزيد :) أيضا ، لمنع ناقلات المشي ، يجب أن يكون عامل النمو أقل من النسبة الذهبية (لن يعمل 1.9)
وأضاف المؤلف HappyCoding, مصدر
يفضل العديد من المبرمجين توزيعات كومة الذاكرة المؤقتة اثنين من أجل تقليل تجزئة الكومة. كما يحصل لك إعادة تخصيص (تسجيل ن) عند إدخالها مرارا وتكرارا.
وأضاف المؤلف abalabazn, مصدر
hoffmale كل عامل نمو ثابت يفعل ، نعم. إلا أن تخصيص كمية الذاكرة التي تحتاجها فقط قد يحتاج إلى مليار تخصيص إذا قمت بإضافة مليار عنصر واحدًا تلو الآخر. كانت النظرية المضادة لإضاعة الذاكرة: إذا قمت بالكثير من تغيير حجم الكائنات الصغيرة أو إلغاء تخصيصها ، فقد ينتهي الأمر في الواقع بالكثير من كتل الكومة الصغيرة التي لا يمكن استخدامها ، ولكن هذا يستغرق وقتًا طويلاً للبحث.
وأضاف المؤلف abalabazn, مصدر
hoffmale أضافت مقطعًا إلى إجابتي عن ذلك.
وأضاف المؤلف abalabazn, مصدر
hoffmale هذا أكثر منطقية. شكرا للتوضيح. ما زال القلق هو أنك قد تأخذ 62 بايت من كتلة 64 بايت ، كما أعتقد ، وترك مجموعة من الثقوب في الكومة صغيرة جدًا لتكون مفيدة.
وأضاف المؤلف abalabazn, مصدر

بعض الأشياء التي يمكنك تحسينها:

Use more of and

الحلقات الخام هي مطول وغير قابل للقراءة. استخدم الخوارزميات كلما استطعت - لقد لاحظت أنك قمت بذلك في بعض الحالات ، ولكنها تركت الحلقات الخام في الآخرين (cf احتياطي ). يبدو أنك لا تعرف أيضًا std :: uninitialized_fill ، وهو ما يفعل بالضبط ما تفعله يدويًا في المُنشئ الثالث ، أو std :: destroy الموجود هنا لتحقيق ما تفعله في clear ( std :: destroy_at هو لعناصر فردية ، كما في popBack ).

بناة بعوامل

يمكن للمهندسين استدعاء الصانعين الآخرين. يتجنب بعض النسخ اللصق. لذلك ، معطى:

template
Vector::Vector(unsigned int size) : //an argument of 0 leads to a     capacity value of 1, distinct from the default ctor
    m_size(size), 
    Log(ceil(log((double)size)/log(2.0))), 
    m_capacity(1 << Log), 
    buff(size ? new T[m_capacity] : nullptr) {}

بإمكانك أن تأخذ:

template 
Vector::Vector(unsigned size, const T& value) : Vector(size) {
    std::copy(buffer, buffer+size, value); 
}

تبسيط destructor الخاص بك

لا تحتاج إلى إعادة ضبط جميع المتغيرات الخاصة بالأعضاء في الشخص المدمر ، حيث لا يمكنك استخدامه بعد ذلك على أي حال.

11
وأضاف

يوجد خطأ في pushBack ، يجب أن تأخذ بالحسبان الحالة التي تسميها فيها عضو موجود في الصفيف.

على سبيل المثال في التعليمات البرمجية

Vector v(1,0);
v.pushBack(v[0]);

pushBack will first resize the array, invalidating the reference to v[0]. It will then attempt to insert that invalid reference in the new array.

10
وأضاف
واو ، تجاهل ذلك تماما مع كل القضايا إدارة الذاكرة الأخرى!
وأضاف المؤلف HappyCoding, مصدر
في الواقع ، هذا السلوك غير محدد في هذه الحالة المحددة. Vector v (1، 0) تهيئة m_capacity خطأ ، وبالتالي v.pushBack (v [0]) قد لا يعيد تخصيصه بالفعل.
وأضاف المؤلف HappyCoding, مصدر

استخدم الأسماء المعروفة للطرق

c_begin and c_end are so close to the standard-container cbegin and cend that users will forever curse you. Likewise for pushBack and popBack (instead of the usual push_back and pop_back).

استخدم نظام تسمية متناسق داخل الفصل

يبدو أن لدى الأعضاء مزيجًا من أنظمة التسمية: buff و m_size و Log . اختيار واحد والتشبث به.

تجنب التعليمات غير الضرورية

في المدمر ، لدينا:

buff = nullptr;
m_size = 0;
m_capacity = 0;
Log = 0;

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

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

تسقط طريقة clear() الذاكرة

قمنا بتعيين buff على مؤشر فارغ بدون حذف مورده - هناك delete [] buff مفقودة هناك. من الجدير حقًا إجراء اختبارات الوحدة تحت Valgrind من حين لآخر للقبض على أشياء كهذه.

لا تحذف المحتويات في المهمة ...

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

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

Use the correct names of functions from

يبدو أن المترجم يمارس حقه في تعريف الأسماء في مساحة الاسم العامة وكذلك في std . ولكنها ليست محمولة للاعتماد على ذلك ، لذا اتصل std :: log و std :: ceil بأسمائها الكاملة.

استخدم ملف واحد

يجب أن تكون تعريفات الأسلوب مرئية للمستخدمين (كما هي قوالب) ، لذلك يجب أن يكونوا في ملف الرأس مع التعريفات.

10
وأضاف
شكرًاhoffmale - الآن أرى clear() عضوًا ، وأرى خللًا في ذلك ، لذلك قمت بتحريره وفقًا لذلك.
وأضاف المؤلف Toby Speight, مصدر
في الواقع ، هناك وظيفة العضو clear() ...
وأضاف المؤلف HappyCoding, مصدر

مشاكل

مشكلتك الرئيسية هي أن استخدامك للموضع الجديد (ودعوة destructor اليدوي) هي نتائج السلوك غير صحيحة وغير محددة لأن عمر الكائنات غير متناسق.

مراجعة التعليمات البرمجية

يبدو من المحتمل قليلاً أن تصطدم مع أشخاص آخرين

#ifndef VECTOR_H
#define VECTOR_H

لديك لجعل هذه فريدة تشمل مساحة الاسم الخاص بك في الحرس. الحديث عن مساحة الاسم إضافة مساحة اسم حول الرمز الخاص بك.

استخدام inline هو تثبيط.

    inline unsigned int capacity() const { return m_capacity; }
    inline unsigned int size() const { return m_size; }
    inline bool empty() const { return m_size == 0; }
    inline iterator begin() { return buff; }
    inline iterator end() { return buff + (m_size - 1) * sizeof(T); }
    inline const_iterator c_begin() { return buff; }
    inline const_iterator c_end() { return buff + (m_size - 1) * sizeof(T); }

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

هذه تبدو خاطئة.

    inline iterator end() { return buff + (m_size - 1) * sizeof(T); }
    inline const_iterator c_end() { return buff + (m_size - 1) * sizeof(T); }

ليست هناك حاجة لإضافة sizeof (T) في تلك التعبيرات. عندما تقوم بزيادة المؤشر الذي تضيفه حسب حجم الكائن الذي يشير إليه.

يبدو أنك بحاجة إلى الإصدارات const من هذين (بالإضافة إلى الإصدارات غير const).

    T& front();
    T& back();
    T& operator [](unsigned int index);

لديك الصانعين التحرك. لماذا لا نقل الإدراج.

    void pushBack(const T& val);

لماذا لا تستخدم فقط معلمة افتراضية في المُنشئ التالي لتطبيق هذا الإصدار من المُنشئ؟

template
Vector::Vector() : 
    m_capacity(0),
    m_size(0),
    Log(0),
    buff(nullptr) {}

ترتيب قائمة التهيئة غير ذي صلة.

template
Vector::Vector(unsigned int size) : //an argument of 0 leads to a capacity value of 1, distinct from the default ctor
    m_size(size), 
    Log(ceil(log((double)size)/log(2.0))), 
    m_capacity(1 << Log), 
    buff(size ? new T[m_capacity] : nullptr) {}

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

ونتيجة لذلك ، لم تتم تهيئة جميع الأعضاء بشكل صحيح لأن البعض يعتمد على القيم التي لم يتم تهيئتها بعد.

الترتيب الفعلي الذي سيتم القيام به هو:

    m_capacity(1 << Log),                   //Notice here Log is used
                                            //But has not been initialized.
    m_size(size), 
    Log(ceil(log((double)size)/log(2.0))), 
    buff(size ? new T[m_capacity] : nullptr) {}

الاستخدام غير الصحيح للموضع الجديد.

    buff(size ? new T[m_capacity] : nullptr)  //You are calling the constructor of `T` for every object in this array
    ....
    new(buffer + i) T(initial);               //You are over writing an object that has had its constructor called.

لا يمكن استخدام الموضع الجديد إلا على ذاكرة RAW. إنك تستخدمه هنا على كائن مباشر بالفعل (كائن تم استدعاء منشئه بالفعل). هذا هو سلوك غير معروف.

كان من الأصح استخدام مشغل التعيين.

من المستحسن وضع علامة على منشئ الخطوة كـ noexcept .

template
Vector::Vector(Vector && v)
    : m_size(0),
    m_capacity(0),
    buff(nullptr),
    Log(0)
{
    swap(v);
}

هذا يسمح لبعض التحسينات من قبل المجمع. وحيث أن المكالمة الوحيدة هي التبديل() ، فيجب أن يكون هذا صحيحًا (عادةً ما يكون التبادل غير موجود).

لا يوفر مشغل التعيين ضمان الاستثناء القوي. كما أنها ليست ذاتية الاحالة آمنة. إذا قمت بتعيين هذا الكائن لنفسه ، فسيكون له سلوك غير معروف.

template
Vector& Vector::operator =(Vector const& v)
// //not strong exception, but will write a wrapper that makes it strong exception; lowest level -> fastest
{
    delete[] buff;
    buff = nullptr;
    buff = v.m_capacity ? new T[v.m_capacity * sizeof(T)] : nullptr;
    m_size = v.m_size;
    m_capacity = v.m_capacity;
    Log = v.Log;
    std::copy(v.buff, v.buff + m_size-1, buff);
    return *this;
}

أراك تلاحظ ذلك في تعليقاتكم. من المستحيل جعل هذا الاستثناء آمنًا باستخدام نسخة ونسخة المبادلة (وليس له مزيد من الحمل أكثر من القيام به يدويًا). أيضا نسخة ونسخة المبادلة هي آمنة مهمة ذاتية.

مرة أخرى نقل المشغل. عادة ما يتم وضع علامة كـ noexcept . لسوء الحظ ، عند الاتصال به ، لا يمكنك فعل ذلك!

template
Vector& Vector::operator =(Vector && v)
{
    delete[] buff; //prep this
    m_size = 0;
    buff = nullptr;
    m_capacity = 0;
    Log = 0;

    swap(v);

    return *this;
}

ليست هناك حاجة للقيام بحذف هنا. ببساطة تفعل المبادلة. سيسمح لك ذلك بوضع علامة على الطريقة noexcept . بعد قيامك بإجراء مكالمة مبادلة مسح على RHS لفرض الكائنات في المتجه إلى إتلاف (لتقليد المتطلبات على std :: vector). السماح لل destructor من RHS تنظيف الذاكرة للمتجه.

كما يضيف أيضًا فرصة لتحسين الاستخدام (حيث يمكن إعادة استخدام التخزين).

عمل إضافي يجري القيام به.

template
Vector::~Vector()
{
    delete[] buff;

   //Everything below this point is a waste of time.
   //Don't do it.
   //Also assigning null to the pointer can potentially hide errors. When you are debugging.
    buff = nullptr;
    m_size = 0;
    m_capacity = 0;
    Log = 0;
}

ستجعلك هذه الدعوة إلى المدمر في ورطة.

template
void Vector::popBack()
{
    (reinterpret_cast(buff)[m_size-- - 1]).~T();
}

المشكلة هي أن buff هو مؤشر إلى T ، لذلك عندما تتصل بالحذف في destructor فستستدعي destructor لـ T. عن طريق الاتصال به هنا انتهيت من الكائنات فترة العمر حتى استدعاء الحذف في destructor هو سلوك غير معرف.

كتبت 5 مقالات حول بناء ناقلات.

Vector - Resource Management Allocation
Vector - Resource Management Copy Swap
Vector - Resize
Vector - Simple Optimizations
Vector - The Other Stuff

8
وأضاف

نوع معلمات حجمك

حالياً ، لديك مثل unsigned int ، لكن هذا غالباً ما يختلف عن size_t و ptrdiff_t . المثال الأكثر شيوعًا في عام 2018 هو أن معظم برامج التحويل البرمجي 64 بت تحدد غير الموقّع int بحجم 32 بت ، مما يحد من نواقلك إلى 4GiB. من الممكن أيضًا أن تتخيل تطبيقات ذات 16 أو 24 بت size_t ولكن ALU أوسع (برنامج Windows أو DOS ذو 16 بت يعمل على 80386 أو أفضل؟) يستخدم إصدار STL size_t ، بعرض 32 بتًا على الأنظمة الأساسية 32 بت و 64 بتًا على الأنظمة الأساسية 64 بت.

ستجد في كثير من الأحيان بعض الأشخاص الذين يفضلون التوقيع على مؤشرات غير موقعة ، على أساس أنه من السهل جدًا على الحساب أن يعطيك رقمًا سلبيًا ، وأن تفسير ذلك على أنه عدد كبير غير موقعة سيؤدي إلى أخطاء منطقية. هذا له مشكلة قابلية النقل الخاصة به ، في ذلك الفائض غير الموقعة والتدفقات السفلية محددة بشكل جيد ولكن لا يتم تسجيل التدفق الفائض أو الحد الأدنى ، ولكن جميع عمليات التنفيذ الفعلية تقريبًا تستخدم الرياضيات المكملتين. إذا كنت ترغب في استخدام مؤشرات موقعة ، فذلك النوع هو ptrdiff_t ، وهو نوع نتيجة طرح مؤشر واحد من آخر.

إن الحل المفضل لدى Microsoft هو rsize_t ، وهو ملحق اختياري لمعيار لغة C. هذه الكمية غير الموقعة التي تشير أعلامها العلوية إلى أنها غير صالحة (أي size_t التي من المفترض أن تقوم المكتبة بالتحقق من تجاوزها بشكل صارم).

حجم التخصيص

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

ومع ذلك ، فإن إضافة طريقة .shrink_to_fit() تعطي رمز العميل القدرة على تقرير أن الذاكرة المهدورة مصدر قلق أكبر لهذا التطبيق من تجزئة الكومة. إذا لم يتصل التطبيق بذلك ، فسيحصلون على السلوك الافتراضي.

7
وأضاف