السلام عليكم ،
عدت للسي بلس بلس بعد سنة من هجرانها واستخدام Java ،وكأي شخص جرّب طريقة أفضل في تمرير عدد لا محدود من البارمترات إلى الدوال و التي تقدمها الجافا ، سيجد أن الأمور بحاجة إلى إعادة نظر .
الطريقة التقليدية في لغة " سي " هي استخدام va_list و أخواتها ، بمعنى أننا نتحدث عن macro في لغة " سي " . فمثلاً لو أردنا كتابة دالة تستقبل عدد غير محدود من البارمترات وتقوم بجمع تلك البارمترات/ المعاملات ، ومن ثم إعادة الناتج .
باستخدام Macro
#include <stdarg.h>
#include <iostream>
using namespace std;
template<typename T> T sum(int length, ...)
{
va_list vList;
va_start(vList, length);
T total = 0;
for(int i = 1; i <= length; i++) {
int a = va_arg(vList, T);
total = total + a ;
}
va_end(vList);
return total;
}
int main()
{
const int NUM_OF_ARGUMENTS = 3;
cout << "The summation of : 1,2,3 " << "is:" << sum<int>(NUM_OF_ARGUMENTS,100,300,200)<< endl;
return 0;
}الملاحظات :
- استخدمنا Template لدعم أي نوع من المتغيرات
- استخدمنا va_list وأخواتها لدعم عدد لامحدود من من البارمترات
- الكود السابق بشع ، يعتمد على أسلوب لغة سي القديم ، ويقولون أنه غير آمن
- لاحظ أن أول بارمتر في الدالة sum هو عدد البارمترات .. وهي طريقة سيئة بالمقارنة مع ماتقدمه اللغات الأخرى ، وحتى إن وجدنا طريقة للاستغناء عن أول بارمتر ، فسيبقى شكل الدالة بشع
قارن الكود السابق بطريقة جافا :
static int sum (int ... numbers)
{
int total = 0;
for (int i = 0; i < numbers.length; i++)
total += numbers ;
return total;
}كود نظيف .. رائع ، آمن ، مفهوم وقل ما شئت ، ويمكن أن تدعم أي نوع وليس فقط int باستخدام Object أو Generic في لغة جافا . يمكن أن نحاكي طريقة " جافا " في التعامل مع البارمترات الغير محدودة ، باستخدام مصفوفة ديناميكية ، سأستخدم std::vector و يمكن أن تستخدم طرق أفضل و أسرع ، مثل تلك الموجودة في boost .
استخدام std::vector
#include <stdarg.h>
#include <iostream>
#include <vector>
using namespace std;
template<typename T> T sum(const std::vector<T> &numbers)
{
T total = 0;
for(int i = 0; i <= numbers.size(); i++) {
total = total + numbers ;
}
return total;
}
int main()
{
std::vector<int> arguments;
arguments.push_back(100);
arguments.push_back(200);
arguments.push_back(300);
cout << "The summation of : 1,2,3 " << "is:" << sum<int>(arguments)<< endl;
return 0;
}الكود السابق أكثر جمالاً والأهم أنه أكثر مقروئية ، ويمكنك أن تزينه جمالاً بالقراءة عن Boost.Assign
ولكن بعد قياس سرعة كل طريقة على جهاز واحد ذو مواصفات جيدة جداً وباستخدام Release طبعاً وجدت التالي :
- نجد أن std::vector : تكلفنا 47 جزء من الثانية .
- بينما Macro : تكلفنا صفر جزء من الثانية :D .
سأضع كود التجربة لاحقاً
واضح جداً أن فرق السرعة لصالح Macro ولكن يمكن استخدام طرق أسرع من std::vector ، ولنجرب شيء تافه نقوم به .. لنجرب أن نصنع " Class " على الماشي ، ليمثل المصفوفة الديناميكة التي نريد ولنستخدمها كبديل لـ std::vector .
استخدام مصفوفة ديناميكية بسيطة DynamicArray
لاحظوا :
#include <stdarg.h>
#include <iostream>
#include <vector>
#include <windows.h>
#include <stdio.h>
#include <assert.h>
using namespace std;
template <typename T>
class DynamicArray
{
T *a;
int size; // max size
int length; // current length
public:
DynamicArray<T>(int size)
:size(size),length(0)
{
// suppose that there is no exception to throw !
a = new T[size];
}
bool add(const T &value){
if(length > size) return false;
a[length++] = value;
return true;
}
const T& get(int index) const{
assert(index < length );
return a[index];
}
int getLength() const{
return this->length;
}
};
template<typename T> T sumDynamicArray(const DynamicArray<T> &numbers)
{
T total = 0;
for(int i=0;i<numbers.getLength();i++){
total = total + numbers.get(i);
}
return total;
}
int main()
{
DynamicArray<int> arr(10);
arr.add(100);
arr.add(200);
arr.add(300);
cout << sumDynamicArray<int>(arr) << endl ;
return 0;
}الآن ، قمت بإعادة حساب تكلفة كل طريقة ، والنتيجة مذهلة !
- طريقة std::vector : تكلفنا 47 جزء من الثانية .
- طريقة macro : تكلفنا صفر جزء من الثانية .
- طريقة DynamicArray : تكلفنا 15 جزء من الثانية ( كأسوأ تقدير ) .
نلاحظ عزيز القارئ ، أننا و صلنا لمرحلة متقدمة جداً من الوصول لمستوى قريب من طريقة Macro وهذا ما دفعني للطمع واستخدام Boost.array ، وكانت تلك القشة التي قصمت ظهر va_list ،
استخدام boost::array
template<typename T,std::size_t E> T sumBoostArray(const boost::array<T,E> &numbers)
{
T total = 0;
for(int i=0;i<numbers.size();i++){
total = total + numbers;
}
return total;
}
int main()
{
boost::array<int,3> arr = { { 100, 200, 300} };
std::cout << sumBoostArray(arr) << std::endl;
return 0;
}الآن ، قمت بإعادة حساب تكلفة كل طريقة ، والنتيجة مذهلة !
- طريقة std::vector : تكلفنا 47 جزء من الثانية .
- طريقة macro : تكلفنا صفر جزء من الثانية .
- طريقة DynamicArray : تكلفنا 15 جزء من الثانية ( كأسوأ تقدير ) .
- طريقة boost::array : تكلفنا صفر جزء من الثانية !
عيوب الطرق السابقة
الطرق السابقة يعيبها شيء واحد ، وهو أنه لا يمكنك التحكم بنوع كل بارمتر لوحده ، فلا تستطيع مثلاً أن تكتب :
void printListOfArgs(... args){
for (arg : args){
cout << arg <<endl;
}
}
int main()
{
int i = 10;
float f = 20.6f;
char c = 'c';
std::string s = "string";
Object obj("Name","age","Addresss");
printListOfArgs(i,f,c,s,obj);
return 0;
}هناك خيوط قد تقود لحل هذا الإشكال ، لعل boost:tuple والذي سيكون ضمن c++0x على ما اذكر ، ولكن لا أعرف إن كان يمكن استخدامه ليحل المشكلة التي نتحدث عنها في هذا المقال ! حاولت تسخيره لحل الاشكال لكن يبدو أنه عسيراً بعض الشيء .
هل تملك فكرة ؟
من الحلول أيضاً ، Variadic Templates ، وهي من ضمن مواصفات C++0x ، وشرحها Stroustrup في موقعه :
http://www2.research.att.com/~bs/C++0xFAQ.html#variadic-templates
أيضاً / هل تملك فكرة عن هذه الطريقة ؟
الخلاصة
أجد أن استخدام boost::array والتي أظن أنها أصبحت ضمن المقاييس في C++0x ، هي البديل المناسب لـ Macro وذلك من أجل حل مشكلة " الدالة ذات البارمترات الغير محدودة " ، يمكن أن ألخص مزايا هذه الطريقة بالنقاط التالية :
- استخدام boost::array سريع بما فيه الكفاية لدرجة أنه قد لايوجد فرق إطلاقاً بينه و بين Macro va_list و أخواتها
- استخدام boost::array يعني كود نظيف ، واضح ، آمن ، ويقوم بالمهمة على أفضل وأكمل وجه
- باستخدام boost::array ، لن تضيف عبء على المبرمج الذي يريد استخدام ميزة الدوال ذات البامرترات الغير محدودة ، فلن تحتاج إلا إلى أساسيات ++C بعكس va_list والتي تعتبر أسلوب شاذ عن المألوف وتتطلب وقت لاستيعابها و فهمها ، بل أن حتى فهمها بشكل منطقي يبدو أنه مطلب عسير
- إذا لم تتمكن من استخدام boost::array في مشروعك ، فيمكنك بناء مصفوفة ديناميكية خاصة بك ، وقد تصل لمستوى قريب جداً من boost::array ببضعة أسطر
كود المقارنات
مكتوب بأسلوب بسيط ، فليس الهدف التأكد 100% من صحة المقارنة ،
تم تطبيق المقارنة على جهاز محمول يحمل هذه المواصفات :
Intel Core 2 Due 2.10 GHz
Ram 3 Giga
Windows XP SP3
Visual C++ 2008 and GCC
أتمنى أن تكون يا مبرمج " سي بلس بلس "، أزلت فكرة استخدام macro لحل مشكلة البارمترات الغير محدودة !
