سؤال

في لغة C++، ما هي البدائل المتاحة لي لعرض مجموعة، من وجهة نظر الأداء وسلامة البيانات؟

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

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

المحلول

إجابة RichQ هي تقنية معقولة، إذا كنت تستخدم مصفوفة، أو متجهًا، أو ما إلى ذلك.

إذا كنت تستخدم مجموعة لم تتم فهرستها بواسطة القيم الترتيبية...أو أعتقد أنك قد تحتاج إلى في وقت ما في المستقبل القريب..ثم قد ترغب في التفكير في الكشف عن نوع (أنواع) التكرارات الخاصة بك وما يرتبط بها begin()/end() طُرق:

class Blah
{
public:
   typedef std::vector<mydata> mydata_collection;
   typedef myDataCollection::const_iterator mydata_const_iterator;

   // ...

   mydata_const_iterator data_begin() const 
      { return myPreciousData.begin(); }
   mydata_const_iterator data_end() const 
      { return myPreciousData.end(); }

private:
   mydata_collection  myPreciousData;
};

... والتي يمكنك بعد ذلك استخدامها بالطريقة العادية:

Blah blah;
for (Blah::mydata_const_iterator itr = blah.data_begin();
   itr != blah.data_end();
   ++itr)
{
   // ...
}

نصائح أخرى

في كثير من الأحيان يريد المتصل الوصول فقط للتكرار على المجموعة.خذ صفحة من كتاب روبي واجعل التكرار جانبًا خاصًا لفصلك.

#include <algorithm>
#include <boost/function.hpp>

class Blah
{
  public:
     void for_each_data(const std::function<void(const mydata&)>& f) const
     {
         std::for_each(myPreciousData.begin(), myPreciousData.end(), f);
     }

  private:
     typedef std::vector<mydata> mydata_collection;
     mydata_collection  myPreciousData;
};

مع هذا النهج، فإنك لا تكشف أي شيء عن مكوناتك الداخلية، أي:حتى أنك يملك مجموعة.

ربما شيء من هذا القبيل؟

const std::vector<mydata>& getData()
{
  return _myPrivateData;
}

الفائدة هنا هي أن الأمر بسيط للغاية وآمن بقدر ما تحصل عليه في لغة C++.يمكنك إرسال هذا، كما يقترح RobQ، ولكن لا يوجد شيء يمكنك القيام به من شأنه أن يمنع أي شخص من ذلك إذا كنت لا تقوم بالنسخ.هنا، سيكون عليك استخدام const_cast, ، وهو أمر يسهل اكتشافه إذا كنت تبحث عنه.

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

لن يساعد استخدام مرجع const أو المؤشر المشترك إلا إذا لم تتغير محتويات المجموعة الأساسية بمرور الوقت.

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

يمكنك تمرير مرجع إلى ناقل النتيجة إلى الوظيفة.في بعض المترجمين قد يؤدي ذلك إلى تعليمات برمجية أسرع بشكل هامشي.

أوصي بمحاولة إعادة التصميم أولاً، ثم استخدام حل نظيف ثانيًا، وتحسين الأداء ثالثًا (إذا لزم الأمر).

إحدى ميزات حلول @Shog9's و @RichQ هي أنها تفصل العميل عن تنفيذ المجموعة.

إذا قررت تغيير نوع مجموعتك إلى شيء آخر، فسيظل عملاؤك يعملون.

ما تريده هو الوصول للقراءة فقط دون نسخ مجموعة البيانات الكبيرة بالكامل.لديك خياران.

أولاً، يمكنك فقط إرجاع مرجع const إلى أي حاوية بيانات لديك، كما هو مقترح أعلاه:

const std::vector<T>& getData() { return mData; }

هذا له عيب الملموسة:لا يمكنك تغيير كيفية تخزين البيانات داخليًا دون تغيير واجهة فصلك.

ثانيًا، يمكنك إرجاع مؤشرات const-ed إلى البيانات الفعلية:

const T* getDataAt(size_t index)
{
   return &mData[index];
}

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

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

ربما تكون أسهل طريقة لإدارة ذلك هي استخدام Boost Range:

typedef vector<T>::const_iterator range_iterator_type;
boost::iterator_range< range_iterator_type >& getDataRange()
{
    return boost::iterator_range(mData.begin(), mData.end());
}

يتمتع هذا بمزايا كون النطاقات قابلة للتركيب والتصفية وما إلى ذلك، كما ترون في موقع إلكتروني.

يعد استخدام const خيارًا معقولًا.قد ترغب أيضًا في الاطلاع على مكتبة Boost C++ لتطبيق المؤشر المشترك الخاص بهم.ويوفر مزايا المؤشرات أي.قد يكون لديك متطلب لإرجاع مؤشر مشترك إلى "فارغ" وهو ما لا يسمح به المرجع.

http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

في حالتك، ستجعل نوع المؤشر المشترك ثابتًا لمنع الكتابة.

اذا كان لديك std::list من البيانات القديمة البسيطة (ما قد يطلق عليه .NET "أنواع القيمة")، ثم سيكون من الجيد إرجاع مرجع ثابت إلى تلك القائمة (تجاهل الأشياء الشريرة مثل const_cast)

اذا كان لديك std::list من المؤشرات (أو boost::shared_ptr's) فإن ذلك لن يؤدي إلا إلى منعك من تعديل المجموعة، وليس العناصر في المجموعة.إن لغة C++ الخاصة بي صدئة جدًا بحيث لا أستطيع إخبارك بالإجابة على ذلك في هذه المرحلة :-(

أقترح استخدام عمليات الاسترجاعات على غرار EnumChildWindows.سيكون عليك إيجاد بعض الوسائل لمنع المستخدم من تغيير بياناتك.ربما استخدم أ const المؤشر/المرجع.

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

MyClass tmp;
for(int i = 0; i < n; i++){
    tmp = elements[i];
    callback(tmp);
}

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

الجزء 1: التغليف ومصاصي الدماء
الجزء الثاني (التسجيل المجاني مطلوب الآن لقراءة هذا): اكتشاف حطام القطار
بواسطة كيفلين هيني

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top