سؤال

أحاول أن أتعرف على الصفوف (شكرًا @litb)، والاقتراح الشائع لاستخدامها هو للوظائف التي تُرجع > قيمة واحدة.

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

استعارة مثال, ، سأستخدم هذا

struct divide_result {
    int quotient;
    int remainder;
};

باستخدام Tuple، سيكون لديك

typedef boost::tuple<int, int> divide_result;

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

struct divide_result {
    int results[2]; // 0 is quotient, 1 is remainder, I think
};

... الأمر الذي لن يملأني بالثقة.

وماذا في ذلك نكون مزايا الصفوف على الهياكل التي تعوض الغموض؟

هل كانت مفيدة؟

المحلول

الصفوف

أعتقد أنني أتفق معك في أن مسألة الموضع الذي يتوافق مع المتغير يمكن أن تسبب ارتباكًا.ولكن أعتقد أن هناك وجهان.واحد هو جانب المكالمة والآخر هو جانب المستدعي:

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

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

معلمات الإخراج

يمكن استخدام معلمات الإخراج أيضًا بالطبع:

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

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

إرجاع هيكل

الخيار الثالث هو استخدام البنية:

div_result d = div(10, 3);

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

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

cout << div(10, 3);

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

نصائح أخرى

وثمة خيار آخر هو استخدام خريطة دفعة فيوجن (رمز مجربة):

struct quotient;
struct remainder;

using boost::fusion::map;
using boost::fusion::pair;

typedef map<
    pair< quotient, int >,
    pair< remainder, int >
> div_result;

ويمكنك الوصول إلى النتائج حدسي نسبيا:

using boost::fusion::at_key;

res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);

وهناك مزايا أخرى أيضا، مثل القدرة على أعاد على حقول من الخريطة، الخ الخ انظر في الدوكو للحصول على مزيد من المعلومات.

ومع الصفوف، يمكنك استخدام tie، وهو في بعض الأحيان مفيدة للغاية: std::tr1::tie (quotient, remainder) = do_division ();. هذا ليس من السهل مع البنيات. ثانيا، عند استخدام رمز قالب، أنه في بعض الأحيان أسهل على الاعتماد على أزواج من إضافة بعد typedef وآخر لنوع البنية.

وإذا كانت أنواع مختلفة، ثم زوج / الصفوف (tuple) هو في الحقيقة ليس أسوأ من البنية. أعتقد على سبيل المثال pair<int, bool> readFromFile()، حيث int هو عدد وحدات البايت القراءة ومنطقي هو ما إذا كان قد تم ضرب الفولكلوري. إضافة البنية في هذه الحالة يبدو وكأنه مبالغة بالنسبة لي، خصوصا أن هناك أي غموض هنا.

تعد Tuples مفيدة جدًا في لغات مثل ML أو Haskell.

في C++، بناء الجملة يجعلها أقل أناقة، ولكن يمكن أن تكون مفيدة في المواقف التالية:

  • لديك دالة يجب أن تُرجع أكثر من وسيطة واحدة، لكن النتيجة تكون "محلية" للمتصل والمستدعى؛لا تريد تحديد هيكل لهذا الغرض فقط

  • يمكنك استخدام وظيفة التعادل للقيام بنموذج محدود جدًا من مطابقة الأنماط "a la ML"، وهو أكثر أناقة من استخدام بنية لنفس الغرض.

  • أنها تأتي مع عوامل تشغيل < محددة مسبقًا، والتي يمكن أن توفر الوقت.

وأنا أميل إلى استخدام الصفوف جنبا إلى جنب مع typedefs على الأقل جزئيا تخفيف 'الصفوف (tuple) المجهولون "المشكلة. على سبيل المثال إذا كان بنية الشبكة ثم:

//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;

وبعد ذلك يمكنني استخدام نوع اسمه:

grid_index find(const grid& g, int value);

وهذا هو مثال مفتعلة نوعا ما ولكن أعتقد أن معظم الوقت الذي يضرب وسيلة سعيدة بين القراءة، الوضوح، وسهولة الاستخدام.

وأو في المثال الخاص بك:

//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);

وميزة واحدة من المجموعات التي لم يكن لديك مع البنيات هي في التهيئة الخاصة بهم. النظر في شيء كما يلي:

struct A
{
  int a;
  int b;
};

وإذا لم تكتب أي ما يعادل make_tuple أو منشئ ثم استخدام هذا الهيكل كمعلمة إدخال عليك أولا إنشاء كائن مؤقت:

void foo (A const & a)
{
  // ...
}

void bar ()
{
   A dummy = { 1, 2 };
   foo (dummy);
}

وليس سيئا جدا، ومع ذلك، تأخذ القضية حيث يضيف صيانة عضوا جديدا إلى البنية دينا لأي سبب من الأسباب:

struct A
{
  int a;
  int b;
  int c;
};

وقواعد التهيئة الكلية يعني في الواقع أن نظامنا سوف تستمر في ترجمة دون تغيير. ولذلك لدينا للبحث عن كافة الأعراف المتبعة في هذه البنية وتحديثها، من دون أي مساعدة من مترجم.

وعلى النقيض من هذا مع الصفوف (tuple):

typedef boost::tuple<int, int, int> Tuple;
enum {
  A
  , B
  , C
};

void foo (Tuple const & p) {
}

void bar ()
{
  foo (boost::make_tuple (1, 2));  // Compile error
}

والمترجم لا يمكن initailize "الصفوف (tuple)" مع نتيجة make_tuple، وهكذا يولد الخطأ الذي يسمح لك بتحديد القيم الصحيحة للمعلمة ثالثة.

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

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}

ويمنع التعليمات البرمجية التي تناثرت مع العديد من التعريفات لبنية. انه من الاسهل بالنسبة للشخص كتابة رمز، والبعض استخدامه عند توثيق فقط ما كل عنصر في الصفوف (tuple) هو، بدلا من كتابة البنية الخاصة بك / جعل الناس ينظرون عن تعريف البنية.

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

وأنا أتفق معك 100% رودي.

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

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

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

Tuples هي الشر.

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