Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...

<none>

Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
9 + 8 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.

أخبار تكنلوجيا

أنت هنا

رقم الخبر عنوان الخبر التفاصيل
27,060 الدرس 33: توليد الأعداد العشوائية في Cpp

مولد قيم عشوائية حقيقيّة

لإنشاء قيم عشوائية حقّا (generate true random) يمكن استخدامها في التشفير، يجب استخدام ‎std::random_device‎ كمُولِّد.

#include <iostream> #include <random> int main() { std::random_device crypto_random_generator; std::uniform_int_distribution < int > int_distribution(0, 9); int actual_distribution[10] = {0,0,0,0,0,0,0,0,0,0}; for (int i = 0; i < 10000; i++) { int result = int_distribution(crypto_random_generator); actual_distribution[result]++; } for (int i = 0; i < 10; i++) { std::cout << actual_distribution[i] << " "; } return 0; }

تُستخدم std::random_device بنفس طريقة استخدام مولّد القيم العشوائية الزائفة (pseudo random value).

ورغم ذلك فيمكن تنفيذ ‎std::random_device‎ انطلاقًا من محرّك أعداد عشوائية زائفة تُحدد وفق التنفيذ في حال لم يتوفّر مصدر غير حتمي (non-deterministic) مثل جهاز خاص بتوليد القيم العشوائية لاستخدامه في التنفيذ.

يمكن الكشف عن مثل هذه التنفيذات من خلال الدالة التابعة ‎entropy‎ (التي تعيد 0 في حال كان المولد حتميًّا تمامًا)، لكنّ العديد من المكتبات الشائعة (مثل libstdc++‎ و LLVM's libc++‎) تعيد دائمًا القيمة 0، حتى عند استخدام مولّدات عشوائية خارجية عالية الجودة .

توليد عدد عشوائي زائف

تنشئ مولّدات الأعداد شبه العشوائية قيمًا يمكن تخمينها استنادًا إلى القيم التي تم توليدها سابقًا، هذا يعني أنها حتمية. ولا تستخدم مولّدات الأعداد شبه العشوائية في الحالات التي تستلزم أعدادًا عشوائية حقًّا.

#include <iostream> #include <random> int main() { std::default_random_engine pseudo_random_generator; std::uniform_int_distribution < int > int_distribution(0, 9); int actual_distribution[10] = {0,0,0,0,0,0,0,0,0,0}; for (int i = 0; i < 10000; i++) { int result = int_distribution(pseudo_random_generator); actual_distribution[result]++; } for (int i = 0; i <= 9; i++) { std::cout << actual_distribution[i] << " "; } return 0; }

تنشئ هذه الشيفرة مولّدَ أعدادٍ عشوائية زائفة، وتوزيعًا يولّد أعدادًا صحيحة في نطاق [0،9] باحتمال متساوي، ويحسب المولّد بعد ذلك عدد مرّات إنشاء كل نتيجة.

يحدّد معامل القالب ‎std::uniform_int_distribution<T>‎ نوع العدد الصحيح الذي يجب إنشاؤه. استخدم ‎std::uniform_real_distribution<T>‎ لتوليد الأعداد العشرية (floats) والأعداد العشرية المزدوجة (doubles).

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

من الممكن استخدام مولّد الأعداد العشوائية مع عدة توزيعات، ويجب ذلك حقيقة.

#include <iostream> #include <random> int main() { std::default_random_engine pseudo_random_generator; std::uniform_int_distribution < int > int_distribution(0, 9); std::uniform_real_distribution < float > float_distribution(0.0, 1.0); std::discrete_distribution rigged_dice({1,1,1,1,1,100}); std::cout << int_distribution(pseudo_random_generator) << std::endl; std::cout << float_distribution(pseudo_random_generator) << std::endl; std::cout << (rigged_dice(pseudo_random_generator) + 1) << std::endl; return 0; }

عرّفنا في هذا المثال مولِّدًا واحدًا فقط، ثمّ استخدمناه لإنشاء أعداد عشوائية في ثلاثة توزيعات مختلفة، وسيولّد التوزيع ‎rigged_dice‎ قيمة بين 0 و 5، لكن سيولّد في الأغلبية الساحقة من الحالات ‎5‎، لأنّ احتمال إنشاء ‎5‎ يساوي ‎100 / 105‎.

هذا الدرس جزء من سلسلة دروس عن C++‎.

ترجمة -بتصرّف- للفصل Chapter 65: Random number generation من كتاب C++ Notes for Professionals

27,059 الدرس 32: خوارزميات المكتبة القياسية std في Cpp

std::next_permutation

المثال التالي يبدل تسلسل المجال [first, last] ويحوّله إلى التبديل التالي الأعلى في الترتيب المعجمي (lexicographically higher permutation)، ويمكن تخصيص قاعدة التبديل عبر ‎cmpFun‎.

template < class Iterator > bool next_permutation(Iterator first, Iterator last); template < class Iterator, class Compare > bool next_permutation(Iterator first, Iterator last, Compare cmpFun); المعاملات
  • ‎first‎ - بداية المجال المُراد تبديله (مُضمّن)
  • ‎last‎ - نهاية المجال المراد تبديله (غير مُضمّن)
القيمة المعادة

تعيد true إن كان التبديل موجودًا، وخلاف ذلك، يُحوّل المجال إلى أصغر تبديل معجمية (lexicographically smallest permutation)، ثم تُعاد القيمة false.

التعقيد Complexity

التعقيد يساوي O(n)‎، حيث تمثّل n المسافة من ‎first‎ إلى ‎last‎. إليك المثال التالي:

std::vector< int > v { 1, 2, 3 }; do { for (int i = 0; i < v.size(); i += 1) { std::cout << v[i]; } std::cout << std::endl; } while (std::next_permutation(v.begin(), v.end()));

هذا يطبع جميع تقليبات المجال 1،2،3 وفق ترتيب معجمي تصاعدي.

الخرج:

123 132 213 231 312 321 std::for_each

في الشيفرة أدناه، نطبّق الدالّة ‎f‎ على نتيجة تحصيل كلّ مُكرّر في المجال ‎[first, last)‎ بدءًا من ‎first‎ وحتى ‎last - 1‎.

template < class InputIterator, class Function > Function for_each(InputIterator first, InputIterator last, Function f); المعاملات
  • ‎first, last‎ - المجال الذي ستُطبّق ‎f‎ عليه.
  • ‎f‎ - كائن قابل للاستدعاء يُطبّق على نتيجة تحصيل كل مكرّر في المجال [‎first, last‎).
القيمة المُعادة

تعاد ‎f‎ إن كان الإصدار أقدم من C++‎ 11، وإلّا فستُعاد std::move(f)‎.

التعقيد

تُطبّق ‎f‎ عدد ‎last - first‎ مرّة.

مثال

الإصدار ≥ C++‎ 11

std::vector<int> v { 1, 2, 4, 8, 16 }; std::for_each(v.begin(), v.end(), [](int elem) { std::cout << elem << " "; });

تطبيق الدالّة المُعطاة على كل عنصر من المتجه ‎v‎ يؤدي إلى طباعة هذا العنصر في مجرى الخرج ‎stdout‎.

std::accumulate

هذه الخوارزمية مُعرّّفة في الترويسة

template < class InputIterator, class T > T accumulate(InputIterator first, InputIterator last, T init); // (1) template < class InputIterator, class T, class BinaryOperation > T accumulate(InputIterator first, InputIterator last, T init, BinaryOperation f); // (2)

تُجرى std::accumulate عملية الطي (fold operation) باستخدام الدالّة ‎f‎ على المجال ‎[first, last)‎ بدءا من ‎init‎ كقيمة تراكمية (accumulator value)، وهذا يكافئ:

T acc = init; for (auto it = first; first != last; ++it) acc = f(acc, * it); return acc;

في الإصدار (1)، يُستخدم ‎operator+‎ بدلًا من ‎f‎، لذا فإنّ مراكمة قيمِ حاويةٍ يعادل مجموع عناصر تلك الحاوية.

المعاملات
  • ‎first, last‎ - المجال الذي ستُطبّق ‎f‎ عليه.
  • ‎init‎ - القيمة الأولية للتراكم.
  • ‎f‎ - دالّة الطي الثنائية (binary folding function).
القيمة المُعادة

القيمة المتراكمة الناتجة عن التطبيق المتتابع للدالّة ‎f‎.

التعقيد

التعقيد يساوي O(n × k)‎، و n هنا يمثّل المسافة من ‎first‎ إلى ‎last‎، فيما تمثّل O(k)‎ تعقيد الدالّة ‎f‎. انظر المثال البسيط التالي عن مُراكمة الجَمْع:

std::vector<int> v { 2, 3, 4 }; auto sum = std::accumulate(v.begin(), v.end(), 1); std::cout << sum << std::endl;

الناتج سيكون:

10

تحويل الأرقام (digits) إلى عدد (number):

الإصدار<C++‎ 11

class Converter { public: int operator()(int a, int d) const { return a * 10 + d; } };

ثمّ:

const int ds[3] = {1, 2, 3}; int n = std::accumulate(ds, ds + 3, 0, Converter()); std::cout << n << std::endl;

الإصدار ≥ C++‎ 11

const std::vector<int> ds = {1, 2, 3}; int n = std::accumulate(ds.begin(), ds.end(), 0, [](int a, int d) { return a * 10 + d; }); std::cout << n << std::endl;

الناتج سيكون:

123 std::find

تساعد std::find على العثور على أوّل ظهور لعنصر val داخل مجال ‎[first, last)‎‎‎.

template < class InputIterator, class T > InputIterator find (InputIterator first, InputIterator last, const T& val); المعاملات
  • ‎first‎ : يمثل مُكرّرًا يشير إلى بداية المجال.
  • ‎last‎ يمثل مُكرّرًا يشير إلى نهاية المجال.
  • ‎val‎ يمثل القيمة المبحوث عنها داخل المجال.
القيمة المعادة

يُعاد مكرِّر يشير إلى أوّل عنصر في المجال يساوي (==) val، أما إن لم يكن هناك عنصر يحقّق ذلك، فسيُعاد مكرّر يشير إلى last. انظر المثال التالي:

#include <vector> #include <algorithm> #include <iostream> using namespace std; int main(int argc, const char * argv[]) {

سننشئ متجهًا هنا:

vector<int> intVec {4, 6, 8, 9, 10, 30, 55,100, 45, 2, 4, 7, 9, 43, 48};

ثم نعرِّف المكرِّرات:

vector < int > ::iterator itr_9; vector < int > ::iterator itr_43; vector < int > ::iterator itr_50;

والآن نستدعي find:

itr_9 = find(intVec.begin(), intVec.end(), 9); //occurs twice itr_43 = find(intVec.begin(), intVec.end(), 43); //occurs once // قيمة غير موجودة في المتجهة itr_50 = find(intVec.begin(), intVec.end(), 50); //does not occur cout << "first occurrence of: " << * itr_9 << endl; cout << "only occurrence of: " << * itr_43 << endl;

نستطيع إثبات أن itr_9 تشير إلى الظهور الأول لـ 9 عبر فحص القيمة التي تلي 9، والتي يجب أن تكون 10 وليس 43، نتابع:

cout << "element after first 9: " << * (itr_9 + 1) << ends;

وسنلقي نظرة على العنصر الموجود قبل النهاية لتجنب تحصيل ()intVec.end:

cout << "last element: " << * (itr_50 - 1) << endl; return 0; }

يكون ناتج ذلك كله:

first occurrence of: 9 only occurrence of: 43 element after first 9: 10 last element: 48 std::min_element

تُستخدم std::min_element لإيجاد أصغر عنصر في مجال معيّن.

template < class ForwardIterator > ForwardIterator min_element(ForwardIterator first, ForwardIterator last); template < class ForwardIterator, class Compare > ForwardIterator min_element(ForwardIterator first, ForwardIterator last, Compare comp); المعاملات
  • ‎first‎ - مكرّر يشير إلى بداية المجال
  • ‎last‎ - مكرّر يشير إلى نهاية المجال
  • ‎comp‎ - مؤشّر دالّة أو كائن دالّة يأخذ وسيطين ويعيد إما true أو false موضحًا إذا كان الوسيط الأول أصغر من الوسيط الثاني. يُشتَرَط ألًا تعدّل هذه الدالّة المُدخلاتِ.
القيمة المعادة

يُعاد مكرّر إلى أصغر عنصر في المجال.

التعقيد

التعقيد يساوي O(1)‎ - n‎، حيث يمثل n عدد العناصر المُقارنَة. انظر المثال التالي:

#include <iostream> #include <algorithm> #include <vector> #include <utility> // make_pair لاستخدام using namespace std; // دالّة تقارن زوجين bool pairLessThanFunction(const pair < string, int > & p1, const pair < string, int > & p2) { return p1.second < p2.second; } int main(int argc, const char * argv[]) { vector<int> intVec {30,200,167,56,75,94,10,73,52,6,39,43}; vector> pairVector = {make_pair("y", 25), make_pair("b", 2), make_pair("z", 26), make_pair("e", 5) }; // < العامل الافتراضي هو auto minInt = min_element(intVec.begin(), intVec.end()); // pairLessThanFunction استخدام auto minPairFunction = min_element(pairVector.begin(), pairVector.end(), pairLessThanFunction); // intVector اطبع أصغر قيمة في cout << "min int from default: " << * minInt << endl; // pairVector اطبع أصغر قيمة في cout << "min pair from PairLessThanFunction: " << ( * minPairFunction).second << endl; return 0; }

الناتج سيكون:

min int from default: 6 min pair from PairLessThanFunction: 2 std::find_if

تُستخدم std::find_if لإيجاد أوّل عنصر في المجال يحقّق دالّة شرطية ‎pred‎.

template < class InputIterator, class UnaryPredicate > InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred); المُعاملات
  • ‎first‎ => مكرّر يشير إلى بداية المجال
  • ‎last‎ => مكرّر يشير إلى نهاية المجال
  • ‎pred‎ => دالّة شرطية - predicate - (تعيد إمّا true أو false)
القيمة المعادة

يُعاد مكرّر يشير إلى أول عنصر في المجال يحقّق الدالّة الشرطية، وسيُعاد مكرّر إلى last في حال لم يكن هناك أيّ عنصر يحقّق ذلك. انظر المثال التالي:

#include <iostream> #include <vector> #include <algorithm> using namespace std; /* عرِّف بعض الدوالّ الشرطية */ // يساوي 10 x إن كان true إعادة bool multOf10(int x) { return x % 10 == 0; } // إن كان العنصر أكبر من القيمة الممرّرة true إعادة class Greater { int _than; public: Greater(int th): _than(th) { } bool operator()(int data) const { return data > _than; } }; int main() { vector myvec {2, 5, 6, 10, 56, 7, 48, 89, 850, 7, 456}; // lambda مع دالّة vector < int > ::iterator gt10 = find_if(myvec.begin(), myvec.end(), [](int x) { return x > 10; }); // >= // مع مؤشّر دالّة vector < int > ::iterator pow10 = find_if(myvec.begin(), myvec.end(), multOf10); // مع كائن دالّي vector < int > ::iterator gt5 = find_if(myvec.begin(), myvec.end(), Greater(5)); // غير موجود vector < int > ::iterator nf = find_if(myvec.begin(), myvec.end(), Greater(1000)); // nf points to myvec.end(); // myvec.end() التحقّق مما إذا كان المؤشّر يشير إلى if (nf != myvec.end()) { cout << "nf points to: " << * nf << endl; } else { cout << "item not found" << endl; } cout << "First item > 10: " << * gt10 << endl; cout << "First Item n * 10: " << * pow10 << endl; cout << "First Item > 5: " << * gt5 << endl; return 0; }

الناتج سيكون:

item not found First item > 10: 56 First Item n * 10: 10 First Item > 5: 6 استخدام std::nth_element لإيجاد الوسيط

تأخذ خوارزمية ‎std::nth_element‎ ثلاثة مُكرِّرات: مكرّر يشير إلى البداية، وآخر يشير إلى الموضع n، وآخر يشير إلى النهاية. وبمجرد عودة الدالّة، سيكون العنصر رقم n (في الترتيب وليس في الموضع) هو أصغر n عنصر. تحتوي الدالّة على تحميلات زائدة أكثر تفصيلًا، على سبيل المثال، بعضها تأخذ كائنات دالّية للموازنة.

ملاحظة: هذه الدالّة فعّالة للغاية - إذ أنّ تعقيدها خطّي.

في المثال التالي، سنُعرّف الوسيط median، وهو القيمة التي تفصل النصف الأعلى من التسلسل عن النصف الأصغر بحيث يتساوى على طرفيه عدد القيم بعد ترتيبها تصاعديًا، أي أنه العنصر الأوسط في الترتيب. للتّبسيط، سنُعرّف وسيط تسلسل مكوّن من n عنصر على أنّه العنصر الموجود في الموضع ⌈ n / 2 ⌉ حسب الترتيب. مثلًا، سيكون وسيط تسلسل طوله 5 هو العنصر الأصغر الثالث، وكذلك وسيط تسلسل بطول 6 عناصر.

لاستخدام هذه الدالّة لإيجاد الوسيط، يمكننا استخدام ما يلي، لنبدأ بــ:

std::vector v{5, 1, 2, 3, 4}; std::vector < int > ::iterator b = v.begin(); std::vector < int > ::iterator e = v.end(); std::vector < int > ::iterator med = b; std::advance(med, v.size() / 2); // الوسيط موجود في الموضع الثاني std::nth_element(b, med, e); // v[2] الوسيط الآن هو

ولإيجاد نقطة تجزئة من الدرجة p ‏‏(p-th quantile‏‏)، سنغيّر بعض السطور أعلاه:

const std::size_t pos = p * std::distance(b, e); std::advance(nth, pos);

ستكون نقطة التجزئة في الموضع ‎pos‎.

std::count

تحسُب std::count عدد العناصر التي تساوي قيمةً معيّنة val:

template < class InputIterator, class T > typename iterator_traits < InputIterator > ::difference_type count(InputIterator first, InputIterator last, const T & val); المعاملات
  • ‎first‎ - مكرّر يشير إلى بداية المجال. *‎last‎ => مكرّر يشير إلى نهاية المجال.
  • ‎val‎ - القيمة المراد حساب تكرار حدوثها في المجال.
القيمة المعادة

عدد العناصر التي تساوي (==) val في المجال. انظر المثال التالي:

#include <vector> #include <algorithm> #include <iostream> using namespace std; int main(int argc, const char * argv[]) { // إنشاء متجهة vector intVec{4,6,8,9,10,30,55,100,45,2,4,7,9,43,48}; //9, 55, 101 حساب مرات حدوث size_t count_9 = count(intVec.begin(), intVec.end(), 9); //occurs twice size_t count_55 = count(intVec.begin(), intVec.end(), 55); //occurs once size_t count_101 = count(intVec.begin(), intVec.end(), 101); //occurs once // اطبع النتيجة cout << "There are " << count_9 << " 9s" << endl; cout << "There is " << count_55 << " 55" << endl; cout << "There is " << count_101 << " 101" << ends; // 4 ابحث عن أول عنصر في المتجهة يساوي vector < int > ::iterator itr_4 = find(intVec.begin(), intVec.end(), 4); // حساب مرات حدوثه في المتجه size_t count_4 = count(itr_4, intVec.end(), * itr_4); // should be 2 cout << "There are " << count_4 << " " << * itr_4 << endl; return 0; }

الناتج سيكون:

There are 2 9s There is 1 55 There is 0 101 There are 2 4 std::count_if

تحسب std::count_if عدد العناصر في المجال التي تحقّق شرطًا معينًا.

template < class InputIterator, class UnaryPredicate > typename iterator_traits < InputIterator > ::difference_type count_if(InputIterator first, InputIterator last, UnaryPredicate red); المعاملات
  • ‎first‎ - مكرّر يشير إلى بداية المجال.
  • ‎last‎ - مكرّر يشير إلى نهاية المجال.
  • ‎red‎ - دالّة شرطية (تعيد إما true أو false).
القيمة المعادة

عدد العناصر في المجال التي تحقّق الشرط. انظر المثال التالي:

#include <iostream> #include <vector> #include <algorithm> using namespace std; /* عرِّف بعض الدوال لاستخدامها كشروط */ // فرديا number إن كان true إعادة bool isOdd(int i) { return i % 2 == 1; } // أكبر من قيمة وسيط المنشِئ number إن كان true كائن دالي يعيد provided class Greater { int _than; public: Greater(int th): _than(th) {} bool operator()(int i) { return i > _than; } }; int main(int argc, const char * argv[]) { // أنشئ متجهًا vector myvec = {1,5,8,0,7,6,4,5,2,1,5,0,6,9,7}; // لحساب عدد العناصر الزوجية lambda استخدام دالّة size_t evenCount = count_if(myvec.begin(), myvec.end(), [](int i) { return i % 2 == 0; }); // >= C++11 // استخدام مؤشّر دالّة لحساب عدد الأعداد الفردية في النصف الأول من المتجهة size_t oddCount = count_if(myvec.begin(), myvec.end() - myvec.size() / 2, isOdd); // استخدام كائن دالّي لحساب عدد العناصر الأصغر من 5 size_t greaterCount = count_if(myvec.begin(), myvec.end(), Greater(5)); cout << "vector size: " << myvec.size() << endl; cout << "even numbers: " << evenCount << " found" << endl; cout << "odd numbers: " << oddCount << " found" << endl; cout << "numbers > 5: " << greaterCount << " found" << endl; return 0; }

الناتج سيكون:

vector size: 15 even numbers: 7 found odd numbers: 4 found numbers > 5: 6 found

هذا الدرس جزء من سلسلة دروس عن C++‎.

ترجمة -بتصرّف- للفصل Chapter 62: Standard Library Algorithms من كتاب C++ Notes for Professionals

27,058 الدرس 31: ما يجدر بك معرفته عما في المكتبة القياسية std في Cpp

std::optional: القيم الاختيارية

تُستخدم القيم الاختيارية (المعروفة أيضًا باسم "أنواع الشّك") لتمثيل نوع قد يكون محتواه موجودًا أو لا، وقد قُدِّمت في C++‎ 17 على هيئة صنف ‎std::optional‎. فمثلًا، قد يحتوي كائن من النوع ‎std::optional<int>‎ على قيمة من النوع ‎int‎، أو قد لا يحتوي على أيّ قيمة. وتُستخدَم القيم الاختيارية إمّا لتمثيل قيمة قد لا تكون موجودة، أو كنوعٍ للقيمة المعادة من دالّة قد تفشل أحيانًا في إعادة نتيجة ذات معنى.

استخدام قيمة اختيارية لتمثيل غياب القيمة

كانت المؤشّرات التي تحمل القيمة ‎nullptr‎ قبل C++‎ 17 تمثّل عدم وجود أيّ قيمة، يعد هذا حلاً جيدًا للكائنات الكبيرة التي خُصِّصت ديناميكيًا وتُدار بواسطة مؤشّرات. لكن لا يعمل هذا الحلّ بشكل جيّد مع الأنواع الصغيرة أو الأوّلية (primitive) مثل ‎int‎، والتي نادرًا ما تُخصّص أو تُدار ديناميكيًا من قبل المؤشّرات، ويوفّر ‎std::optional‎ حلاً مثاليًا لهذه المشكلة الشائعة.

في المثال أدناه، عرّفنا البنية Person‎، والتي تمثل شخصًا، هذا الشخص يمكنه أن يمتلك حيوانًا أليفًا (pet)، لكنّ ذلك ليس ضروريًا. ولكي نخبر المصرّف بأنّ الحقل pet اختياري هنا، فسنُصرّح عنه بواسطة المُغلّف ‎std::optional‎.

#include <iostream> #include <optional> #include <string> struct Animal { std::string name; }; struct Person { std::string name; std::optional < Animal > pet; }; int main() { Person person; person.name = "John"; if (person.pet) { std::cout << person.name << "'s pet's name is " << person.pet - > name << std::endl; } else { std::cout << person.name << " is alone." << std::endl; } } القيم الاختيارية كقيمة معادة

انظر المثال التالي:

std::optional < float > divide(float a, float b) { if (b != 0. f) return a / b; return {}; }

في المثال أعلاه، سنعيد الكسر ‎a/b‎، ولكن إذا لم يكن الكسر مُعرّفا (إن كان ‎b‎ يساوي 0 مثلًا)، فسَنعيد القيمة الاختيارية الفارغة. هذا مثال أكثر تعقيدًا:

template < class Range, class Pred > auto find_if( Range&& r, Pred&& p ) { using std::begin; using std::end; auto b = begin(r), e = end(r); auto r = std::find_if(b, e, p); using iterator = decltype(r); if (r == e) return std::optional < iterator > (); return std::optional < iterator > (r); } template < class Range, class T > auto find( Range&& r, T const& t ) { return find_if( std::forward<Range>(r), [&t](auto&& x){return x==t;} ); }

تبحث الدالّة ‎‎find( some_range, 7 )‎ في الحاوية أو النطاق ‎some_range‎ عن شيء يساوي العدد ‎7‎، وتفعل الدالة ‎find_if‎ عبر دالّة شرطية (predicate)، فتعيد إمّا قيمة اختيارية فارغة إذا لم يُعثر على أيّ شيء يساوي العدد ‎7‎، أو عنصرًا اختياريا يحتوي على مُكرّر إلى العنصر في حال كان موجودًا. هذا يتيح لك القيام بما يلي:

if (find(vec, 7)) { // code }

أو حتى:

if (auto oit = find(vec, 7)) { vec.erase( * oit); }

دون الحاجة إلى استخدام مُكرّرات begin/end أو إجراء الاختبارات.

value_or

انظر المثال التالي:

void print_name( std::ostream& os, std::optional<std::string> const& name ) { std::cout "Name is: " << name.value_or("<name missing>") << '\n'; }

يعيد التابع ‎value_or‎ القيمة المخزّنة في القيمة الاختيارية، أو يعيد الوسيط إذا لم يكن هناك أيّ شيء مُخزّن. يتيح لك هذا إمكانية أخذ القيمة الاختيارية (التي يمكن أن تكون فارغة) وتحديد سلوك افتراضي عندما تكون بحاجة إلى قيمة، وهكذا الطريقة، يمكن ترك تحديد "السلوك الافتراضي" إلى أن تكون هناك حاجة إليه، بدلًا من إنشاء قيمة افتراضية داخل مُحرِّك ما.

مقاربات أخرى للقيم الاختيارية

هناك العديد من الطرق الأخرى لحلّ المشكلة التي تحلها القيم الاختياريّة ‎std::optional‎، لكن لا طريقة كاملة من تلك الطرق:

القيم الاختيارية مقابل المؤشّر

في بعض الحالات، يمكننا تمثيل "اختياريّةِ كائنٍ" عبر توفير مؤشّر يشير إلى كائن موجود أو مؤشّر فارغ ‎nullptr‎ للإشارة إلى فشل العمليّة. ولكنّ استخدام هذه الطريقة يقتصر على الحالات التي تكون فيها الكائنات موجودة بالفعل - بالمقابل، يمكن للقيم الاختيارية ‎optional‎ أن تُستخدَم لإعادة كائنات جديدة دون الحاجة إلى تخصيص الذاكرة.

القيم الاختيارية مقابل القيم التنبيهية

من المقاربات الشائعة استخدام قيمة خاصّة للإشارة إلى أنّ القيمة لا معنى لها، وقد تكون هذه القيمة مثلًا 0 أو ‎-1 بالنسبة للأعداد الصحيحة، أو ‎nullptr‎ بالنسبة للمؤشّرات.

مثلًا، لنفترض أنّ هناك دالّة تحاول العثور على فهرس أول ظهور لحرف في سلسلة نصية، في حال كان الحرف موجودًا في السلسلة النصية، فستعيد فهرس أول ظهور له، أمّا في حال لم يكن موجودًا، فستعيد القيمة ‎-1 للدلالة على أنّ الحرف غير موجود في السلسلة النصية. القيمة ‎-1 تمثل القيمة التنبيهية، لأنها تنبّهنا إلى أمر ما (في هذا المثال، تنبِّهنا إلى عدم وجود الحرف المبحوث عنه في السلسلة النصية).

المشكلة في هذه المقاربة أنّها تقلل من مساحة القيم الصالحة (لا يمكنك التمييز بين القيمة 0 الصالحة والقيمة 0 التي لا معنى لها)، وليست كل الأنواع فيها خيار طبيعي للقيم التنبيهية.

القيم الاختيارية مقابل الأزواج std::pair‎,>

من المقاربات الشائعة أيضًا توفير زوج يكون أحد عُنصَريه قيمةً بوليانية ‎bool‎ للإشارة إلى كون القيمة ذات معنى أم لا، ويشترط هذا أن يكون نوع القيمة قابلًا للإنشاء افتراضيًا (default-constructible) في حالة حدوث خطأ، وهو أمر غير ممكن في بعض الأنواع، وقد يكون ممكنًا ولكن غير مرغوب بالنسبة لأنواع أخرى. بالمقابل، لا تحتاج القيم الاختيارية ‎optional<T>‎ في حال حدوث خطأ إلى بناء أي شيء.

استخدام القيم الاختيارية لتمثيل فشل دالة

قبل C++‎ 17، كانت الدوالّّ تمثِّل الفشلَ عادة بإحدى الطرق التالية:

  • إعادة مؤشّر فارغ.
  • على سبيل المثال، استدعاء دالّة ‎Delegate *App::get_delegate()‎ على نسخة من الصنف ‎App‎ ليس لها مُفوّض (delegate) سيعيد ‎nullptr‎.
  • يُعدّ هذا حلاً جيدًا للكائنات التي خُصِّصت ديناميكيًا أو الكائنات الكبيرة التي تُدار عبر المُؤشّرات، لكنه ليس حلاً جيدًا بالنسبة للكائنات الصغيرة التي عادةً ما تكون مرصوصة (stack-allocated) وتُمرّر عن طريق النسخ.
  • تحجز قيمة محدّدة من النوع المُعاد للإشارة إلى الفشل.
  • على سبيل المثال، قد يؤدي استدعاء دالّة ‎unsigned shortest_path_distance(Vertex a, Vertex b)‎ على رأسين (vertices) غير مُتصلين إلى إعادة 0 للإشارة إلى هذه الحقيقة.
  • يتم إقران القيمة المُعادة مع قيمة بوليانية ‎bool‎ لتحديد ما إذا كانت القيمة المعادة ذات معنى أم لا.
  • على سبيل المثال، يؤدي استدعاء دالّة ‎std::pair<int, bool> parse(const std::string &str)‎ باستخدام وسيط يحتوي سلسلة نصّية لا تتضمّن عددًا صحيحًا إلى إعادة زوج يضمّ عددًا صحيحا غير محدد وقيمة بوليانية تساوي ‎false‎.

في هذا المثال، يُعطى لزيد حيوانين أليفَين، سوسن وسوسان، ثم تُستدعى الدالّة ‎Person::pet_with_name()‎ لاسترداد الكلب "وافي". ونظرًا لأنّ زيد لا يملك كلبًا باسم "وافي"، فإن الدالّة ستفشل، وستُعاد القيمة ‎std::nullopt‎ بدلاً من ذلك.

#include <iostream> #include <optional> #include <string> #include <vector> struct Animal { std::string name; }; struct Person { std::string name; std::vector < Animal > pets; std::optional < Animal > pet_with_name(const std::string & name) { for (const Animal & pet: pets) { if (pet.name == name) { return pet; } } return std::nullopt; } }; int main() { Person john; zaid.name = "زيد"; Animal susan; susan.name = "سوسن"; zaid.pets.push_back(susan); Animal susanne; susanne.name = "سوسان"; zaid.pets.push_back(susanne); std::optional < Animal > dog = john.pet_with_name("وافي"); if (dog) { std::cout << "يملك زيد حيوانًا أليفًا اسمه وافي" << std::endl; } else { std::cout <<”لا يملك زيد حيوانًا أليفًا اسمه وافي" << std::endl; } } std::function: تغليف الكائنات القابلة للاستدعاء

يوضح المثال التالي طريقة استخدام std::function:

#include <iostream> #include <functional> std:: function < void(int, const std::string & ) > myFuncObj; void theFunc(int i, const std::string & s) { std::cout << s << ": " << i << std::endl; } int main(int argc, char * argv[]) { myFuncObj = theFunc; myFuncObj(10, "hello world"); } استخدام std::function مع std::bind

إن احتجت إلى استدعاء دالّة مع تمرير وسائط إليها فإنّ استخدام ‎std::function‎ مع ‎std::bind‎ سيعطيك حلولًا فعّالة للغاية كما هو موضّح أدناه.

class A { public: std:: function < void(int, const std::string & ) > m_CbFunc = nullptr; void foo() { if (m_CbFunc) { m_CbFunc(100, "event fired"); } } }; class B { public: B() { auto aFunc = std::bind( & B::eventHandler, this, std::placeholders::_1, std::placeholders::_2); anObjA.m_CbFunc = aFunc; } void eventHandler(int i, const std::string & s) { std::cout << s << ": " << i << std::endl; } void DoSomethingOnA() { anObjA.foo(); } A anObjA; }; int main(int argc, char * argv[]) { B anObjB; anObjB.DoSomethingOnA(); } ربط std::function مع نوع آخر قابل للاستدعاء

يوضح المثال التالي كيفية استخدام std::function لاستدعاء دالة من نمط C، ودالة تابعة لصنف، وعامل ()operator، ودالة لامدا. ويتم استدعاء الدالة من خلال الوسائط الصحيحة ووسائط بترتيب مختلف، وكذلك بأنواع وأعداد مختلفة.

#include <iostream> #include <functional> #include <iostream> #include <vector> using std::cout; using std::endl; using namespace std::placeholders; // دالّة بسيطة لتُستدعى double foo_fn(int x, float y, double z) { double res = x + y + z; std::cout << "foo_fn called with arguments: " << x << ", " << y << ", " << z << " result is : " << res << std::endl; return res; } // بنية مع دالة تابعة لاستدعائها struct foo_struct { // الدالة المراد استدعاؤها double foo_fn(int x, float y, double z) { double res = x + y + z; std::cout << "foo_struct::foo_fn called with arguments: " << x << ", " << y << ", " << z << " result is : " << res << std::endl; return res; } // هذا التابع له بصمة مختلفة، مع ذلك يمكن استخدامه // لاحظ أنّ ترتيب المعاملات قد تغير double foo_fn_4(int x, double z, float y, long xx) { double res = x + y + z + xx; std::cout << "foo_struct::foo_fn_4 called with arguments: " << x << ", " << z << ", " << y << ", " << xx << " result is : " << res << std::endl; return res; } // جعل الكائن بأكمله قابلا للاستدعاء operator() التحميل الزائد للعامل double operator()(int x, float y, double z) { double res = x + y + z; std::cout << "foo_struct::operator() called with arguments: " << x << ", " << y << ", " << z << " result is : " << res << std::endl; return res; } }; int main(void) { // typedefs using function_type = std:: function < double(int, float, double) > ; // foo_struct نسخة foo_struct fs; // سنخزّن هنا كل الدوالّ المربوطة std::vector < function_type > bindings; // var #1 - يمكن استخدام دالّة بسيطة وحسب function_type var1 = foo_fn; bindings.push_back(var1); // var #2 - يمكنك استخدام تابع function_type var2 = std::bind( & foo_struct::foo_fn, fs, _1, _2, _3); bindings.push_back(var2); // var #3 - يمكنك استخدام تابع مع بصمة مختلفة // لها عدد مختلف من المعاملات ومن أنواع مختلفة foo_fn_4 function_type var3 = std::bind(&foo_struct::foo_fn_4, fs, _1, _3, _2, 0l); bindings.push_back(var3); // var #4 - مُحمَّل تحميلا زائدا operator() يمكنك استخدام كائن ذي معامل function_type var4 = fs; bindings.push_back(var4); // var #5 - lambda يمكنك استخدام دالّة function_type var5 = [](int x, float y, double z) { double res = x + y + z; std::cout << "lambda called with arguments: " << x << ", " << y << ", " << z << " result is : " << res << std::endl; return res; }; bindings.push_back(var5); std::cout << "Test stored functions with arguments: x = 1, y = 2, z = 3" << std::endl; for (auto f: bindings) f(1, 2, 3); }

انظر هذا المثال الحي.

الناتج:

Test stored functions with arguments: x = 1, y = 2, z = 3 foo_fn called with arguments: 1, 2, 3 result is : 6 foo_struct::foo_fn called with arguments: 1, 2, 3 result is : 6 foo_struct::foo_fn_4 called with arguments: 1, 3, 2, 0 result is : 6 foo_struct::operator() called with arguments: 1, 2, 3 result is : 6 lambda called with arguments: 1, 2, 3 result is : 6 تخزين وسائط الدالة في صف std::tuple

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

#include <iostream> #include <functional> #include <tuple> #include <iostream> // دالّة بسيطة لاستدعائها double foo_fn(int x, float y, double z) { double res = x + y + z; std::cout << "foo_fn called. x = " << x << " y = " << y << " z = " << z << " res=" << res; return res; } // مساعِدات من أجل تسريع الصف. template < int... > struct seq {}; template < int N, int...S > struct gens: gens < N - 1, N - 1, S... > {}; template < int...S > struct gens < 0, S... > { typedef seq < S... > type; }; //استدعاء المساعِدات template < typename FN, typename P, int...S > double call_fn_internal(const FN & fn, const P & params, const seq < S... > ) { return fn(std::get < S > (params)...); } // std::tuple استدعاء الدالّة مع الوسائط المُخزنة في template < typename Ret, typename...Args > Ret call_fn(const std:: function < Ret(Args...) > & fn, const std::tuple < Args... > & params) { return call_fn_internal(fn, params, typename gens < sizeof...(Args) > ::type()); } int main(void) { // الوسائط std::tuple < int, float, double > t = std::make_tuple(1, 5, 10); // الدالّة المراد استدعاؤها std:: function < double(int, float, double) > fn = foo_fn; // استدعاء الدالّة مع تمرير الوسائط المُخزّنة إليها call_fn(fn, t); }

هذا مثال حي

الناتج:

foo_fn called. x = 1 y = 5 z = 10 res=16 استخدام std::function مع تعابير لامدا والصنف std::bind #include <iostream> #include <functional> using std::placeholders::_1; // std::bind ستُستخدَم في مثال int stdf_foobar(int x, std:: function < int(int) > moo) { return x + moo(x); // std::function moo استدعاء } int foo (int x) { return 2+x; } int foo_2 (int x, int y) { return 9*x + y; } int main() { int a = 2; /* مؤشّرات الدوالّ */ std::cout << stdf_foobar(a, & foo) << std::endl; // 6 ( 2 + (2+2) ) // stdf_foobar(2, foo) يمكن أن تكون أيضا /* Lambda تعابير */ /* std::function في كائن lambda يمكن تخزين دالّة مُغلقة من تعبير */ std::cout << stdf_foobar(a, [capture_value](int param) -> int { return 7 + capture_value * param; }) << std::endl; // result: 15 == value + (7 * capture_value * value) == 2 + (7 + 3 * 2) /* std::bind تعابير */ /* std::bind يمكن تمرير نتيجة التعبير * مثلا عبر ربط المعاملات باستدعاء مؤشّر دالّة */ int b = stdf_foobar(a, std::bind(foo_2, _1, 3)); std::cout << b << std::endl; // b == 23 == 2 + ( 9*2 + 3 ) int c = stdf_foobar(a, std::bind(foo_2, 5, _1)); std::cout << c << std::endl; // c == 49 == 2 + ( 9*5 + 2 ) return 0; } الحمل الزائد للدوال (function overhead)

يمكن أن تتسبب std::function‎ في حِمل زائد كبير لأنّ std::function‎ لها دلالات قيمية (value semantics)، فسيكون من اللازم أن تنسخ أو تنقل ما استُدعِي إليها. ولكن بما أنها تستطيع أخذ كائن قابل للاستدعاء من أيّ نوع، فسيتعينّ عليها في كثير من الأحيان أن تخصّص ذاكرة ديناميكية بذلك.

تحتوي بعض تطبيقات ‎function‎ على ما يسمى "تحسين الكائنات الصغيرة" (small object optimization)، وفيها تُخزّن الأنواع الصغيرة مثل مؤشّرات الدوالّ أو مؤشّرات الأعضاء أو الكائنات الدالّية (Functors) ذات الحالة الصغيرة، مباشرة في كائن الدالّة ‎function‎. لكنّ هذا لن يعمل إلّا إن كان النوع قابلًا للإنشاء النقلي عند الاعتراض (noexcept move constructible). أيضًا، لا يتطلب معيار C++‎ أن توفّر جميع التطبيقات مثل هذا التحسين. إليك المثال التالي:

// ملف الترويسة using MyPredicate = std::function<bool(const MyValue &, const MyValue &)>; void SortMyContainer(MyContainer &C, const MyPredicate &pred); // الملف المصدري void SortMyContainer(MyContainer &C, const MyPredicate &pred) { std::sort(C.begin(), C.end(), pred); }

يُعدّ معامل القالب هو الحلّ المفضّل لـ ‎SortMyContainer‎، ولكن إن افترضنا أنّ هذا غير ممكن أو غير مرغوب فيه لسبب من الأسباب، فلن تحتاج ‎SortMyContainer‎ إلى تخزين ‎pred‎ إلا في إطار استدعائها. ومع ذلك، فقد تُخصّص لـ ‎pred‎ ذاكرةً إن كان الكائن الدّالِّي (functor) المُعطى ذا حجم غير معروف.

تخصّص ‎function‎ الذاكرة لأنّها تحتاج إلى شيءٍ لتنسخ أو تنقل إليه، كما أنها تأخذ ملكية الكائن القابل للاستدعاء الذي مُرِّر إليها، لكنّ ‎SortMyContainer‎ لا تحتاج إلى امتلاك المُستدعِي ولكنها تحتاج إلى مرجع إليه وحسب، لذا فلا داعي لاستخدام ‎function‎ هنا. كذلك لا يوجد أي نوع دالّة قياسي يشير حصرًا إلى كائنٍ قابل للاستدعاء، لذلك سيكون عليك أن تنتظر إيجاد حلّ لهذا، أو يمكنك التعايش مع الحمل الزائد.

أيضّا، لا تملك ‎function‎ أيّ وسيلة فعّالة للتحكم في الموضع الذي تأتي منه تخصيصات الذاكرة الخاصّة بالكائن. صحيح أنّ لها مُنشئات تأخذ كائن تخصيص ‎allocator‎، ولكنّ العديد من التقديمات لا تقدّمها بالشكل الصحيح … أو لا تقدما بتاتًا.

الإصدار ≥ C++‎ 17

لم يعُد المنشئ ‎function‎ الذي يأخذ كائن تخصيصٍ ‎allocator‎ جزءًا من النوع. لذلك لا توجد أيّ طريقة لإدارة التخصيص. واعلم أن استدعاء ‎function‎ أبطأ من استدعاء المحتويات مباشرةً. كذلك يجب أن يكون الاستدعاء عبر function غير مباشر لأنّ نُسَخَ ‎function‎ يمكن أن تحتوي كائنًا قابلًا للاستدعاء، ويكافئ الحِمل الزائد الناتج عن استدعاء ‎function‎ الحِمل الزائد الناتج عن استدعاء دالّة وهمية.

std::forward_list: القوائم الإدراجية

النوع std::forward_list هو حاوية تدعم الإدراج السريع للعناصر من أي مكان فيها وكذلك إزالة تلك العناصر، لكنها لا تدعم الوصول العشوائي السريع.

يُنفَّذ النوع std::forward_list كقائمة مرتبطة أحادية (singly-linked list)، وليس لها عمومًا أيّ حِمل زائد (overhead) مقارنة بتطبيقها في C. وتوفّر هذه الحاوية، مقارنة بـ ‎std::list‎، مساحة تخزين أكثر كفاءة عند غياب الحاجة للتكرار ثنائي الاتجاه (bidirectional iteration). انظر المثال التالي:

#include <forward_list> #include <string> #include <iostream> template < typename T > std::ostream & operator << (std::ostream & s, const std::forward_list < T > & v) { s.put('['); char comma[3] = { '\0', ' ', '\0' }; for (const auto & e: v) { s << comma << e; comma[0] = ','; } return s << ']'; } int main() { // c++11 صياغة قائمة المهييء في std::forward_list < std::string > words1 { "the", "frogurt", "is", "also", "cursed" }; std::cout << "words1: " << words1 << '\n'; // words2 == words1 std::forward_list < std::string > words2(words1.begin(), words1.end()); std::cout << "words2: " << words2 << '\n'; // words3 == words1 std::forward_list < std::string > words3(words1); std::cout << "words3: " << words3 << '\n'; // words4 is {"Mo", "Mo", "Mo", "Mo", "Mo"} std::forward_list < std::string > words4(5, "Mo"); std::cout << "words4: " << words4 << '\n'; }

الناتج:

words1: [the, frogurt, is, also, cursed] words2: [the, frogurt, is, also, cursed] words3: [the, frogurt, is, also, cursed] words4: [Mo, Mo, Mo, Mo, Mo] التوابع

إليك قائمة التوابع الخاصة بالنوع std::forward_list:

اسم التابع التعريف operator= يعيّن قيمًا إلى الحاوية assign يعيّن قيمًا إلى الحاوية get_allocator يعيد المخصِّص المرتبط به (associated allocator) front يصل إلى العنصر الأول before_begin يعيد مكررًا إلى العنصر قبل البداية cbefore_begin يعيد مكررًا ثابت إلى العنصر قبل البداية begin يعيد مكررًا إلى البداية cbegin يعيد مكررًا ثابت إلى البداية end يعيد مكررًا إلى النهاية cend يعيد مكررًا إلى النهاية empty يتحقق إن كانت الحاوية فارغة max_size يعيد الحد الأقصى للعدد الممكن من العناصر clear يمسح المحتويات insert_after يُدرِج عناصرًا بعد عنصر ما emplace_after ينشئ عنصرًا مكان عنصر آخر erase_after يمحو عنصرًا موجودا بعد عنصر ما push_front يدرج عنصرًا في البداية emplace_front ينشئ عنصرًا في البداية pop_front يزيل العنصر الأول resize يغير عدد العناصر المخزنة swap يبدل المحتويات merge يدمج قائمتين مرتبتين splice_after ينقل عناصر من قائمة أمامية أخرى remove يزيل العناصر التي تحقق شرطا محددا remove_if يزيل العناصر التي تحقق شرطا محددا reverse يعكس ترتيب العناصر unique يزيل العناصر المتساوية المتتالية sort يرتب العناصر table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } std::pair: الأزواج عوامل الموازنة

معامِلات هذه العوامل هي ‎lhs‎ و ‎rhs‎:

  • ‎operator==‎ - يتحقّق هذا العامل من أنّ عناصر كلا الزوجين ‎lhs‎ و ‎rhs‎ متساويان، وتكون القيمة المُعادة هي true إن كان lhs.first == rhs.first و lhs.second == rhs.second، وإلّا فستكون false.
std::pair < int, int > p1 = std::make_pair(1, 2); std::pair < int, int > p2 = std::make_pair(2, 2); if (p1 == p2) std::cout << "equals"; else std::cout << "not equal"; // ستُظهر التعليمة هذا، لأن الزوجين غير متماثلين
  • ‎operator!=‎ - يتحقّق هذا العامل ممّا إذا كان أيّ من عناصر الزوجيين ‎lhs‎ و ‎rhs‎ غير متساويين، وتكون القيمة المُعادة هيtrue إن كان ‎lhs.first != rhs.first أو lhs.second != rhs.second، وإلا فستُعاد القيمة false.
  • ‎operator<‎ - إذا كان lhs.first<rhs.first‎، فسيُعيد true وإن كان rhs.first<lhs.first‎ فسيُعيد false. وكذلك إن كان ‎lhs.second<rhs.second‎ فسيُعيد ‎true‎، أما خلاف ذلك سيُعيد ‎false‎.
  • ‎operator<=‎ - يُعيد ‎!(rhs<lhs)‎
  • ‎operator>‎ - يعيد ‎rhs<lhs
  • ‎operator>=‎ - يعيد ‎!(lhs<rhs)‎

هذا مثال آخر يستخدم حاويات أزواج، ويستخدم العامل ‎operator<‎ لترتيب الحاوية.

#include <iostream> #include <utility> #include <vector> #include <algorithm> #include <string> int main() { std::vector<std::pair<int, std::string>> v = { {2, "baz"}, {2, "bar"}, {1, "foo"} }; std::sort(v.begin(), v.end()); for (const auto & p: v) { std::cout << "(" << p.first << "," << p.second << ") "; // (1,foo) (2,bar) (2,baz) :الناتج } } إنشاء زوج والوصول إلى عناصره

تتيح لنا الأزواج أن نعامل كائِنين كما لو كانا كائنًا واحدًا، ويمكن إنشاء الأزواج بسهولة بمساعدة دالّة القالب std::make_pair. وهناك طريقة أخرى، وهي إنشاء زوج وتعيين عنصُريه (‎first‎ و ‎second‎) لاحقًا.

#include <iostream> #include <utility> int main() { std::pair < int, int > p = std::make_pair(1, 2); // إنشاء الزوج std::cout << p.first << " " << p.second << std::endl; // الوصول إلى العناصر // يمكننا أيضا إنشاء الزوج وتعيين عناصره لاحقا std::pair < int, int > p1; p1.first = 3; p1.second = 4; std::cout << p1.first << " " << p1.second << std::endl; // يمكننا أيضا إنشاء زوج باستخدام منشئ std::pair < int, int > p2 = std::pair < int, int > (5, 6); std::cout << p2.first << " " << p2.second << std::endl; return 0; } std::atomics: الأنواع الذرية

كل استنساخ (instantiation) وتخصيص للقالب ‎std::atomic‎ يعرّف نوعًا ذريًا (atomic type)، فإن قامت أحد الخيوط (threads) بالكتابة في كائن ذرّي أثناء قراءة مسلك آخر منه، فإنّ السّلوك سيكون مُعرّفًا بشكل جيد، ولن يحدث أيّ مشكل.

إضافة إلى ذلك، قد يؤدّي الدخول إلى الكائنات الذرية إلى تهيئة تزامن بين الخيوط ويطلب دخولًا غير ذرّي للذاكرة (non-atomic memory accesses) كما هو مُعرَّف من قِبل ‎std::memory_order‎. يمكن استنساخ std::atomic مع أيّ نوع قابل للنسخ (‎TriviallyCopyable type T. std::atomic‎)، لكن std::atomic ليست قابلة للنسخ أو النقل.

توفّر المكتبة القياسية تخصيصات للقالب std::atomic للأنواع التالية:

1) تعريف تخصيص كامل للنوع البولياني ‎bool‎، وعُرِّفت قيمة التعريف النوعي (typedef) الخاصة به بحيث يُتعامل معه على أنّه نوع ذرّي std::atomic<T>‎ غير مُخصَّص، فيما عدا أنّه ستكون له مخطط (layout) قياسي، ومنشئ افتراضي أولي، ومدمّرات واضحة، كما سيدعم صيغة التهيئة الإجمالية (aggregate initialization syntax):

Typedef التخصيص std::atomic_bool std::atomic<bool>

2) تخصيصات كاملة وتعريفات نوعية typedefs للأنواع العددية الصحيحة، كما يلي:

Typedef التخصيص std::atomic_char std::atomic<char‎>‎ std::atomic_char std::atomic<char>‎ std::atomic_schar std::atomic<signed char>‎ std::atomic_uchar std::atomic<unsigned char>‎ std::atomic_short std::atomic<short>‎ std::atomic_ushort std::atomic<unsigned short>‎ std::atomic_int std::atomic<int>‎ std::atomic_uint std::atomic<unsigned int>‎ std::atomic_long std::atomic<long>‎ std::atomic_ulong std::atomic<unsigned long>‎ std::atomic_llong std::atomic<long long>‎ std::atomic_ullong std::atomic<unsigned long long>‎ std::atomic_char16_t std::atomic<char16_t>‎ std::atomic_char32_t std::atomic<char32_t>‎ std::atomic_wchar_t std::atomic<wchar_t>‎ std::atomic_int8_t std::atomic<std::int8_t>‎ std::atomic_uint8_t std::atomic<std::uint8_t>‎ std::atomic_int16_t std::atomic<std::int16_t>‎ std::atomic_uint16_t std::atomic<std::uint16_t>‎ std::atomic_int32_t std::atomic<std::int32_t>‎ std::atomic_uint32_t std::atomic<std::uint32_t>‎ std::atomic_int64_t std::atomic<std::int64_t>‎ std::atomic_uint64_t std::atomic<std::uint64_t>‎ std::atomic_int_least8_t std::atomic<std::int_least8_t>‎ std::atomic_uint_least8_t std::atomic<std::uint_least8_t>‎ std::atomic_int_least16_t std::atomic<std::int_least16_t>‎ std::atomic_uint_least16_t std::atomic<std::uint_least16_t>‎ std::atomic_int_least32_t std::atomic<std::int_least32_t>‎ std::atomic_uint_least32_t std::atomic<std::uint_least32_t>‎ std::atomic_int_least64_t std::atomic<std::int_least64_t>‎ std::atomic_uint_least64_t std::atomic<std::uint_least64_t>‎ std::atomic_int_fast8_t std::atomic<std::int_fast8_t>‎ std::atomic_uint_fast8_t std::atomic<std::uint_fast8_t>‎ std::atomic_int_fast16_t std::atomic<std::int_fast16_t>‎ std::atomic_uint_fast16_t std::atomic<std::uint_fast16_t>‎ std::atomic_int_fast32_t std::atomic<std::int_fast32_t>‎ std::atomic_uint_fast32_t std::atomic<std::uint_fast32_t>‎ std::atomic_int_fast64_t std::atomic<std::int_fast64_t>‎ std::atomic_uint_fast64_t std::atomic<std::uint_fast64_t>‎ std::atomic_intptr_t std::atomic<std::intptr_t>‎ std::atomic_uintptr_t std::atomic<std::uintptr_t>‎ std::atomic_size_t std::atomic<std::size_t>‎ std::atomic_ptrdiff_t std::atomic<std::ptrdiff_t>‎ std::atomic_intmax_t std::atomic<std::intmax_t>‎ std::atomic_uintmax_t std::atomic<std::uintmax_t>‎

هذا مثال بسيط على استخدام std::atomic_int:‎

#include <iostream> // std::cout #include <atomic> // std::atomic, std::memory_order_relaxed #include <thread> // std::thread std::atomic_int foo(0); void set_foo(int x) { foo.store(x, std::memory_order_relaxed); // تعيين القيمة الذرية } void print_foo() { int x; do { x = foo.load(std::memory_order_relaxed); // الحصول على القيمة الذرّية } while (x == 0); std::cout << "foo: " << x << '\n'; } int main() { std::thread first(print_foo); std::thread second(set_foo, 10); first.join(); //second.join(); return 0; } // foo: 10 std::variant: المتغايرات إنشاء مؤشرات للتوابع الزائفة (Create pseudo-method pointers)

يمكنك استخدام كائن متغَاير (Variant) للشطب الخفيف للنوع (light weight type erasure). انظر المثال التالي:

template < class F > struct pseudo_method { F f; // C++17 السماح باستنتاج نوع الصنف في pseudo_method(F && fin): f(std::move(fin)) {} // عامل بحث كوينج->* لا بأس بما أنه تابع زائف template < class Variant > // متغاير LHS للتحقق من أنّ SFINAE إضافة اختبار friend decltype(auto) operator->*( Variant&& var, pseudo_method const& method ) { // تعيد تعبير لامدا يعيد توجيه استدعاء دالة ما var->*method // مما يجعلها تبدو كأنها تتصرف كمؤشّر تابع return [&](auto&&...args)->decltype(auto) { // للحصول على نوع المتغاير visit استخدم return std::visit( [&](auto&& self)->decltype(auto) { return method.f( decltype(self)(self), decltype(args)(args)... ); }, std::forward < Var > (var) ); }; } };

يؤدي هذا إلى إنشاء نوع يزيد تحميل العامل ‎operator->*‎ بمتغاير ‎Variant‎ على الجانب الأيسر. في المثال التالي، سنستخدم استنتاج نوع الصنف الخاص بـ C++ 17 من أجل إيجاد وسيط القالب لـ print، يجب أن يكون self هو أول وسيط يأخذه تابع لامدا الزائف، ثم يأخذ بقية الوسائط ثم يستدعي الدالة.

pseudo_method print = [](auto&& self, auto&&...args)->decltype(auto) { return decltype(self)(self).print( decltype(args)(args)... ); };

والآن إن كان لدينا نوعان لكل منهما تابع ‎print‎:

struct A { void print(std::ostream & os) const { os << "A"; } }; struct B { void print(std::ostream & os) const { os << "B"; } };

لاحظ أنّهما نوعان غير مترابطان، نستطيع تنفيذ ما يلي:

std::variant<A,B> var = A{}; (var->*print)(std::cout);

سيتم إرسال الاستدعاء مباشرة إلى ‎A::print(std::cout)‎، لكن لو هيأنا ‎var‎ باستخدام ‎B{}‎، فسيتم إرساله إلى ‎B::print(std::cout)‎.

وإذا أنشأنا نوعًا جديدًا C …

struct C {};

… فسيكون لدينا:

std::variant<A,B,C> var = A{}; (var->*print)(std::cout);

ستفشل عملية التصريف، لأنه لا يوجد تابع ‎C.print(std::cout)‎.

سوف يسمح توسيع الشيفرة أعلاه باكتشاف واستخدام دوال ‎print‎، ربّما باستخدام ‎if constexpr‎ ضمن التابع الزائف ‎print‎.

هذا مثال حي يستخدم ‎boost::variant‎ بدلاً من ‎std::variant‎.

الاستخدامات الرئيسية للمتغايرات

تنشي الشيفرة التالية متغايرًا (اتحادًا موسومًا tagged union) يمكنه تخزين إمّا عدد صحيح (‎int‎) وإمّا سلسلة نصية‎string‎.

std::variant< int, std::string > var;

يمكننا تخزين أحد هذين النوعين في الكائن المتغاير:

var = "hello"s;

ويمكننا الوصول إلى مُحتوياته عبر ‎std::visit‎:

// "hello\n" طباعة visit( [](auto&& e) { std::cout << e << '\n'; }, var);

عن طريق تمرير دالّّة لامدا متعددة الأشكال أو كائن دالّّة مشابه، وإذا كنا متأكّدين من النوع، فيمكننا الحصول عليه على النحو التالي:

auto str = std::get<std::string>(var);

ولكن هذا سوف يرفع اعتراضًا إن أخطأنا تقدير النوع.

auto* str = std::get_if<std::string>(&var);

إن أخطأت التقدير فستُعاد القيمة ‎nullptr‎.

تضمن المُتغايرات عدم تخصيص ذاكرة ديناميكية، باستثناء تلك التي تُخصَّص من قبل أنواعها المُضمّنة، ولا يُخزَّن إلا نوع واحد فقط في المتغاير، ويرافقها في حالات نادرة رفع اعتراضات أثناء الإسناد وغياب إمكانية آمنة للتراجع يمكن أن يصبح المتغاير فارغًا.

كما تتيح لك المُتغايرات تخزين قيم من عدّة أنواع في متغيّر واحد بأمان وكفاءة. فهي أساسًا اتحادات‎union‎ ذكية وآمنة.

إنشاء متغاير

هذا لا يشمل المُخصِّصات (allocators).

struct A {}; struct B { B()=default; B(B const&)=default; B(int){}; }; struct C { C()=delete; C(int) {}; C(C const&)=default; }; struct D { D( std::initializer_list<int> ) {}; D(D const&)=default; D()=default; }; std::variant < A, B > var_ab0; // A() يحتوي std::variant < A, B > var_ab1 = 7; // a B(7) يحتوي std::variant < A, B > var_ab2 = var_ab1; // a B(7) يحتوي std::variant < A, B, C > var_abc0 { std::in_place_type < C > , 7 }; // a C(7) يحتوي std::variant < C > var_c0; // C لأجل ctor غير قانوني، لا توجد قيمة افتراضية لـ std::variant<A,D> var_ad0( std::in_place_type<D>, {1,3,3,4} ); // D{1,3,3,4} يحتوي std::variant < A, D > var_ad1(std::in_place_index < 0 > ); // A{} يحتوي std::variant<A,D> var_ad2( std::in_place_index<1>, {1,3,3,4} ); // D{1,3,3,4} يحتوي std::iomanip و std::any std::setprecision

عند استخدام std::setprecision في التعبير ‎out << setprecision(n)‎ أو ‎in >> setprecision(n)‎، فإنّها تضبط معامل الدقة (precision parameter) الخاصّ بمجرى الخرج أو الدخل عند القيمة n.

معامل هذه الدالّة يكون عددًا صحيحًا، ويمثل قيمة الدقة الجديدة. انظر المثال التالي:

#include <iostream> #include <iomanip> #include <cmath> #include <limits> int main() { const long double pi = std::acos(-1.L); std::cout << "default precision (6): " << pi << '\n' << "std::precision(10): " << std::setprecision(10) << pi << '\n' << "max precision: " << std::setprecision(std::numeric_limits < long double > ::digits10 + 1) << pi << '\n'; } //Output // 3.14159 : في الدقة الافتراضية (6) يكون الناتج // std::precision(10): 3.141592654 // 3.141592653589793239 :الدقة القصوى std::setfill

عند استخدام std::setfill في تعبير ‎out << setfill(c)‎، فإنّها تُضبط محرف الملء (fill character) الخاصّ بمجرى الخرج عند القيمة ‎c‎.

ملاحظة: يمكن الحصول على محرف الملء الحالي عبر الدالّة ‎std::ostream::fill‎. مثال:

#include <iostream> #include <iomanip> int main() { std::cout << "default fill: " << std::setw(10) << 42 << '\n' << "setfill('*'): " << std::setfill('*') << std::setw(10) << 42 << '\n'; } // 42 :الافتراضي // setfill('*'): ********42 std::setiosflags

عند استخدام std::setiosflags في التعبير ‎out << setiosflags(mask)‎ أو ‎in >> setiosflags(mask)‎، فإنّها تضبط كل رايات التنسيق (format flags) الخاصّة بمجرى الخرج أو الدخل كما هو محدّد من قِبل القناع (mask).

هذه قائمة بكل رايات ‎std::ios_base::fmtflags‎:

  • ‎dec‎: استخدام أساس عشري لدخل وخرج (I / O) الأعداد الصحيحة
  • ‎oct‎: استخدام أساس ثماني (octal base) لدخل وخرج الأعداد الصحيحة
  • ‎hex‎: استخدام أساس ست عشري (hexadecimal base) لدخل وخرج الأعداد الصحيحة
  • ‎basefield‎ - ‎dec‎|‎oct‎|‎hex‎|‎0‎: مفيدة لتقنيع (masking) العمليات
  • ‎left‎: التعديل الأيسر (إضافة محارف الملْء إلى اليمين)
  • ‎right‎: التعديل الأيمن (إضافة محارف الملء إلى اليسار)
  • ‎internal‎: التعديل الداخلي (إضافة محارف الملء إلى نقطة معيّنة في الداخل)
  • adjustfield - left|right|internal : مفيدة لتقنيع العمليات.
  • ‎scientific‎: توليد الأنواع العددية العشرية باستخدام الصيغة العلمية، أو الصيغة الثمانية (hex notation) في حال اقترنت بـ fixed.
  • ‎fixed‎: توليد الأنواع العددية العشرية باستخدام الصيغة الثابتة (fixed notation)، أو الصيغة الثمانية (hex notation) في حال اقترنت بـ scientific. *‎floatfield‎ - ‎scientific‎|‎fixed‎|(scientific‎|‎fixed‎)|‎0‎: مفيدة لتقنيع لعمليات
  • ‎boolalpha‎: إدراج واستخراج نوع منطقي وفق تنسيق أبجدي رقمي
  • ‎showbase‎: إنشاء سابقة (prefix) تشير إلى الأساس الرقمي لخرج الأعداد الصحيحة، وتتطلّب إشارة إلى العُملة في حال الدخل والخرج الماليّ.
  • ‎showpoint‎: إنشاء محرفَ الفاصلة العشرية (decimal-point character) دون قيد أو شرط لخرج الأعداد العشرية
  • ‎showpos‎: توليد المحرف ‎+‎ للأعداد غير السالبة
  • ‎skipws‎: تخطي المسافات البيضاء الموجودة في البداية قبل عمليات الإدخال
  • ‎unitbuf‎: نقل (flush) الناتج بعد كل عملية خرج
  • ‎uppercase‎: استبدال بعض الأحرف الصغيرة بالأحرف الكبيرة المقابلة لها في بعض مخرجات عمليات الإخراج.

أمثلة على المعدِّلات:

#include <iostream> #include <string> #include<iomanip> int main() { int l_iTemp = 47; std::cout << std::resetiosflags(std::ios_base::basefield); std::cout << std::setiosflags(std::ios_base::oct) << l_iTemp << std::endl; // ==> 57 std::cout << std::resetiosflags(std::ios_base::basefield); std::cout << std::setiosflags(std::ios_base::hex) << l_iTemp << std::endl; // ==> 2f std::cout << std::setiosflags(std::ios_base::uppercase) << l_iTemp << std::endl; // ==> 2F std::cout << std::setfill('0') << std::setw(12); std::cout << std::resetiosflags(std::ios_base::uppercase); std::cout << std::setiosflags(std::ios_base::right) << l_iTemp << std::endl; // ==> 00000000002f std::cout << std::resetiosflags(std::ios_base::basefield | std::ios_base::adjustfield); std::cout << std::setfill('.') << std::setw(10); std::cout << std::setiosflags(std::ios_base::left) << l_iTemp << std::endl; // ==> 47........ std::cout << std::resetiosflags(std::ios_base::adjustfield) << std::setfill('#'); std::cout << std::setiosflags(std::ios_base::internal | std::ios_base::showpos); std::cout << std::setw(10) << l_iTemp << std::endl; // ==> +#######47 double l_dTemp = -1.2; double pi = 3.14159265359; std::cout << pi << " " << l_dTemp << std::endl; // ==> +3.14159 -1.2 std::cout << std::setiosflags(std::ios_base::showpoint) << l_dTemp << std::endl; // ==> -1.20000 std::cout << setiosflags(std::ios_base::scientific) << pi << std::endl; // ==> +3.141593e+00 std::cout << std::resetiosflags(std::ios_base::floatfield); std::cout << setiosflags(std::ios_base::fixed) << pi << std::endl; // ==> +3.141593 bool b = true; std::cout << std::setiosflags(std::ios_base::unitbuf | std::ios_base::boolalpha) << b; // ==> true return 0; } std::setw

انظر المثال التالي حيث يطبع السطر الثاني val في أقصى يسار شاشة الخرج، بينما يطبعها السطر الثالث في حقل إخراج طوله 10 بدءًا من النهاية اليمنى للحقل:

int val = 10; std::cout << val << std::endl; std::cout << std::setw(10) << val << std::endl;

يكون الخرج ما يلي:

10 10 1234567890

(السطر الأخير موجود للمساعدة على رؤية مواضع الأحرف).

عندما نحتاج إلى أن يكون الخرج مُنسّقًا بتنسيق معيّن، فقد نحتاج إلى ضبط عرض الحقل، ويمكن القيام بذلك باستخدام std::setw و std::iomanip. توضّح الشيفرة التالية صيغة ‎std::setw‎:

std::setw(int n)

يمثّل n في هذا المثال طول حقل الخرج الذي سيُعيَّن.

std::any

يوضح المثال التالي كيفية استخدام std::any:

std::any an_object{ std::string("hello world") }; if (an_object.has_value()) { std::cout << std::any_cast<std::string>(an_object) << '\n'; } try { std::any_cast<int>(an_object); } catch(std::bad_any_cast&) { std::cout << "Wrong type\n"; } std::any_cast<std::string&>(an_object) = "42"; std::cout << std::any_cast<std::string>(an_object) << '\n';

المخرجات الناتجة:

hello world Wrong type 42 std::set و std::multiset: المجموعات والمجموعات المتعددة

تمثّل المجموعات "‎set‎" نوعًا من الحاويات عناصرها مُرتّبة وغير مكرّرة، أمّا المجموعات المتعدّدة ‎multiset‎، فتشبه المجموعات العادية، لكن العناصر المتعددة تكون لها نفس القيمة.

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

لدى الصّنفين ‎set‎ و ‎multiset‎ توابع مقارنة افتراضية، ولكن قد تحتاج أحيانًا في بعض الحالات إلى زيادة تحميلها. فمثلًا، لنفترض أنّنا نخزّن سلاسل نصية في مجموعة ما، ونحن نعلم أن تلك السلاسل تحتوي على قيم رقمية فقط. يكون الترتيب الافتراضي قائمًا على المقارنة الأبجدية للسلاسل النصّية، وعليه فلن يتطابق الترتيب مع الترتيب الرقمي. وإن أردت ترتيبها ترتيبًا عدديًا فستحتاج إلى كائن دالّي (functor) لزيادة تحميل تابع الموازنة:

#include <iostream> #include <set> #include <stdlib.h> struct custom_compare final { bool operator()(const std::string & left, const std::string & right) const { int nLeft = atoi(left.c_str()); int nRight = atoi(right.c_str()); return nLeft < nRight; } }; int main() { std::set<std::string> sut({"1", "2", "5", "23", "6", "290"}); std::cout << "### Default sort on std::set<std::string> :" << std::endl; for (auto && data: sut) std::cout << data << std::endl; std::set<std::string, custom_compare> sut_custom({"1", "2", "5", "23", "6", "290"}, custom_compare {}); std::cout << std::endl << "### Custom sort on set :" << std::endl; for (auto && data: sut_custom) std::cout << data << std::endl; auto compare_via_lambda = [](auto &&lhs, auto &&rhs){ return lhs > rhs; }; using set_via_lambda = std::set<std::string, decltype(compare_via_lambda)>; set_via_lambda sut_reverse_via_lambda({"1", "2", "5", "23", "6", "290"}, compare_via_lambda); std::cout << std::endl << "### Lambda sort on set :" << std::endl; for (auto && data: sut_reverse_via_lambda) std::cout << data << std::endl; return 0; }

يكون الخرج ما يلي:

### Default sort on std::set<std::string> : 1 2 23 290 5 6 ### Custom sort on set : 1 2 5 6 23 290 ### Lambda sort on set : 6 5 290 23 2 1

في المثال أعلاه، يمكن استخدام ثلاث طرق مختلفة لإضافة عمليات مقارنة إلى المجموعات "‎std::set‎"، ولكلّ منها فوائدها.

الترتيب الافتراضي

يستخدم الترتيب الافتراضي عامل المقارنة الخاصّ بالمفتاح (الوسيط الأول للقالب)، وغالبًا ما يكون المفتاح إعدادًا افتراضيًا مناسبًا للدالّة ‎std::less<T>‎. وستستخدم هذه الدالة العامل ‎operator<‎ الخاص بالكائن ما لم تكن قد خُصِّصت، هذا مفيد خاصّة عندما تحاول شيفرة أخرى استخدام ترتيب معيّن، إذ يجعل الشيفرة متناسقة.

ستؤدي كتابة الشيفرة بهذه الطريقة إلى تسهيل تحديثها عندما تكون تغييرات المفتاح جزءًا من واجهة برمجية (API)، فمثلًا إن كان لدينا صنف يحتوي على عضوين، وسيتغيّر إلى صنف يحتوي 3 أعضاء، فستُحدَّث جميع النُّسخ عبر تحديث ‎operator<‎ الخاص بالصنف. وكما تتوقع، فإن استخدام التصنيف الافتراضي كخيار افتراضي منطقي ومقبول.

الترتيب المُخصّص

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

يمكن أيضًا استخدام الترتيب المُخصّص في حال كنت ترغب في مقارنة المؤشّرات (الذكية) استنادًا إلى الكائن الذي تشير إليه، أو في حال كنت تحتاج إلى قيود خاصّة في عملية المقارنة، كمقارنة الأزواج ‎std::pair‎ بقيمة العنصر الأول ‎first‎ فقط.

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

وستصادف غالبًا -كما في المثال أعلاه- أصنافًا بدون عوامل مقارنة، وينتج عن هذا منشئاتٌ افتراضية ومنشئاتُ نسخ (copy constructors)، ويسمح لك المُنشئ الافتراضي بحذف النسخة في وقت الإنشاء، كما أنّ منشئ النسخ ضروريّ لأنّ المجموعة تأخذ نسخة من مُعامل المقارنة.

الترتيب عبر تعابير لامدا

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

ما يعيب استخدام تعابير لامدا هو أنّه سيكون لكلّ واحد منها نوع محدّد في وقت التصريف، لذلك سيكون التعبير ‎decltype(lambda)‎ مختلفًا في كل تُصرَّف نفس وحدة التصريف (ملف cpp) عند وجود أكثر من وحدة تصريف تكون مُدرجة في ملفات الترويسة، ولهذا يوصى باستخدام كائنات الدوالّ كعوامل مقارنة عند استخدامها داخل ملفات الترويسة.

سترى هذا النوع من الإنشاء غالبًا عند استخدام مجموعة "‎std::set‎" ضمن النطاق المحلي للدالّة. في حين يُفضَّل استعمال كائن دالّة عند استخدامه كوسيط لدالّة أو كعضو في صنف.

خيارات الترتيب الأخرى

نظرًا لأنّ عامل المقارنة الخاصّ بالمجموعات ‎std::set‎ عبارة عن وسيط قالب، فيمكن استخدام جميع الكائنات القابلة للاستدعاء كعوامل مقارنة، وما الأمثلة أعلاه إلا حالات خاصّة وحسب، ولا توجد قيود على هذه الكائنات القابلة للاستدعاء إلا ما يلي:

  • يجب أن تكون نسخة قابلة للإنشاء النّسخي (copy constructable)
  • ويجب أن تكون قابلة للاستدعاء مع وسيطين من نوع المفتاح نفسه (التحويلات الضمنية مسموح بها رغم عدم استحسانها، لأنها قد تضرّ بالأداء).
حذف قيم من مجموعة

إذا كنت تريد إفراغ المجموعة أو المجموعة المتعدّدة من كل عناصرها، فيمكنك استخدام ‎clear‎:

std::set < int > sut; sut.insert(10); sut.insert(15); sut.insert(22); sut.insert(3); sut.clear(); // يساوي 0 sut حجم

ثم يمكنك استخدام التابع ‎erase‎ الذي يوفّر بعض الوظائف التي تشبه عملية الإدراج:

std::set < int > sut; std::set < int > ::iterator it; sut.insert(10); sut.insert(15); sut.insert(22); sut.insert(3); sut.insert(30); sut.insert(33); sut.insert(45); // الحذف البسيط sut.erase(3); // استخدام مكرّر it = sut.find(22); sut.erase(it); // حذف مجال من القيم it = sut.find(33); sut.erase(it, sut.end()); std::cout << std::endl << "Set under test contains:" << std::endl; for (it = sut.begin(); it != sut.end(); ++it) { std::cout << * it << std::endl; }

الخرج سيكون:

Set under test contains: 10 15 30

تنطبق كلّ هذه التوابع أيضًا على المجموعات المتعدّدة ‎multiset‎، يرجى ملاحظة أنّه في حال طلبت حذف عنصر من مجموعة متعدّدة ‎multiset‎ وكان ذلك العنصر مُكرَّرًا، فستُحذَف جميع العناصر التي تساوي ذلك العنصر.

إدراج قيم في مجموعة

هناك ثلاث طرق لإدراج العناصر في المجموعات.

  1. إدراج بسيط للقيمة باستخدام التابع insert الذي يعيد زوجًا، ممّا يسمح للمُستدعي بالتحقق مما إذا كان الإدراج قد تمّ أم لا.
  2. يمكن الإدراج بإعطاء تلميح عن الموضع الذي ستُدرج فيه القيمة، والهدف من ذلك هو تحسين وقت الإدراج، لكن المشكلة أنّنا لا نعرف دائمًا الموضع الذي يجب أن تُدرج فيه القيمة. انتبه في هذه الحالة لأن طريقة إعطاء التلميح تختلف بحسب إصدارات المصرّفات.
  3. أخيرًا، يمكنك إدراج عدة قيم عن طريق إعطاء مؤشّر للبداية (مُضمّن) والنهاية (غير مُضمّن).
#include <iostream> #include <set> int main() { std::set < int > sut; std::set < int > ::iterator it; std::pair < std::set < int > ::iterator, bool > ret; // إدراج بسيط sut.insert(7); sut.insert(5); sut.insert(12); ret = sut.insert(23); if (ret.second == true) std::cout << "# 23 has been inserted!" << std::endl; ret = sut.insert(23); // بما أنها مجموعة، والعدد 23 موجودا سلفا فيها، فستفشل عملية الإدراج if (ret.second == false) std::cout << "# 23 already present in set!" << std::endl; // إدراج مع تلميح لتسريع الأداء it = sut.end(); // وما بعده C++11 هذه الحالة محسَّنة في // بالنسبة للإصدارات السابقة، يمكن التأشير إلى العنصر الذي يسبق موضع الإدراج sut.insert(it, 30); // إدراج مجال من القيم std::set < int > sut2; sut2.insert(20); sut2.insert(30); sut2.insert(45); std::set < int > ::iterator itStart = sut2.begin(); std::set < int > ::iterator itEnd = sut2.end(); sut.insert(itStart, itEnd); // يُستثنى المكرر الثاني من الإدراج std::cout << std::endl << "Set under test contains:" << std::endl; for (it = sut.begin(); it != sut.end(); ++it) { std::cout << * it << std::endl; } return 0; }

سينتج لنا الخرج التالي:

# 23 has been inserted! # 23 already present in set! Set under test contains: 5 7 12 20 23 30 45 إدراج القيم في مجموعة متعددة

جميع طرق الإدراج الخاصّة بالمجموعات تنطبق أيضًا على المجموعات المتعدّدة، لكن هناك خيار آخر، وهو تمرير قائمة تهيئة initializer_list:

auto il = { 7, 5, 12 }; std::multiset < int > msut; msut.insert(il); البحث عن القيم في المجموعات والمجموعات المتعدّدة

هناك عدّة طرق للبحث عن قيمة معيّنة في مجموعة ‎std::set‎ أو مجموعة متعدّدة ‎std::multiset‎، وللحصول على مُكرِّر يشير إلى موضع أوّل ظهور لمفتاح مُعيّن، يمكن استخدام الدالّة ‎find()‎ التي تعيد ‎end()‎ إذا لم يكن المفتاح موجودًا.

std::set < int > sut; sut.insert(10); sut.insert(15); sut.insert(22); sut.insert(3); // 3, 10, 15, 22 auto itS = sut.find(10); // *itS == 10 القيمة موجودة، لذا itS = sut.find(555); // itS == sut.end() لم يُعثَر على القيمة، لذا std::multiset < int > msut; sut.insert(10); sut.insert(15); sut.insert(22); sut.insert(15); sut.insert(3); // 3, 10, 15, 15, 22 auto itMS = msut.find(10);

الطريقة الأخرى هي استخدام الدالّة ‎count()‎، والتي تحسُب عدد القيم المطابقة التي عُثِر عليها في المجموعة أو المجموعة المتعدّدة (في حالة المجموعات، ستكون القيمة المُعادة إمّا 0 أو 1).

باستخدام نفس القيم المذكورة أعلاه، سيكون لدينا:

int result = sut.count(10); // 1 result = sut.count(555); // 0 result = msut.count(10); // 1 result = msut.count(15); // 2

في حالة المجموعات المتعدّدة، يمكن أن تكون هناك عدّة عناصر لها نفس القيمة، وللحصول على مجالٍ (range) يمثّل تلك العناصر يمكن استخدام الدالّة ‎equal_range()‎ التي تُعيد زوجًا يتألّف من مُكرّر الحدّ الأدنى (مُضمّن) ومُكرّر الحدّ الأعلى (غير مضمَن) على التوالي. وإذا لم يكن المفتاح موجودًا فسيشير كلا المُكرِّران إلى أقرب قيمة عليا وفق تابع المقارنة المُستخدم لترتيب المجموعة المتعدّدة المُعطاة.

auto eqr = msut.equal_range(15); auto st = eqr.first; // '15' يشير إلى العنصر الأول auto en = eqr.second; // '22' يشير إلى العنصر eqr = msut.equal_range(9); // '10' يشيران إلى eqr.second و eqr.first كل من std::integer_sequence: تسلسلات الأعداد الصحيحة

يمثّل قالب الصنف ‎std::integer_sequence<Type, Values...>‎ سلسلة من القيم العددية الصحيحة من نوع ‎Type‎، حيث ‎Type‎ هو أحد أنواع الأعداد الصحيحة المُضمّنة.

تُستخدم هذه التسلسلات عند تنفيذ قوالب الأصناف أو الدوالّ التي تحتاج إلى الوصول الموضعي (positional access)، وتحتوي المكتبة القياسية أيضًا على أنواع مصنَعيّة (factory types) تنشئ تسلسلات تصاعدية من الأعداد الصحيحة انطلاقًا من عدد العناصر المُراد.

تحويل صفّ std::tuple<T...>‎ إلى معاملات دالّة

يمكن استخدام صفّ ‎std::tuple<T...>‎ لتمرير عدّة قيم إلى دالّة، فمثلًا يمكن استخدامه لتخزين سلسلة من المعاملات على شكل صف انتظار (queue)، ويجب تحويل عناصر هذه الصفوف عند معالجتها إلى وسائط استدعاء للدالة.

#include <array> #include <iostream> #include <string> include <tuple> #include <utility> // ---------------------------------------------------------------------------- // الدوالّ المراد استدعاؤها void f(int i, std::string const & s) { std::cout << "f(" << i << ", " << s << ")\n"; } void f(int i, double d, std::string const & s) { std::cout << "f(" << i << ", " << d << ", " << s << ")\n"; } void f(char c, int i, double d, std::string const & s) { std::cout << "f(" << c << ", " << i << ", " << d << ", " << s << ")\n"; } void f(int i, int j, int k) { std::cout << "f(" << i << ", " << j << ", " << k << ")\n"; } // ---------------------------------------------------------------------------- // الدالّة الفعلية التي توسّع الصف template < typename Tuple, std::size_t...I > void process(Tuple const & tuple, std::index_sequence < I... > ) { f(std::get < I > (tuple)...); } // الواجهة المراد استدعاؤها، للأسف يجب أن تُرسل إلى دالّة أخرى لاستخلاص سلسلة الفهارس المُنشأة // std::make_index_sequence<N> من template < typename Tuple > void process(Tuple const & tuple) { process(tuple, std::make_index_sequence < std::tuple_size < Tuple > ::value > ()); } // ---------------------------------------------------------------------------- int main() { process(std::make_tuple(1, 3.14, std::string("foo"))); process(std::make_tuple('a', 2, 2.71, std::string("bar"))); process(std::make_pair(3, std::string("pair"))); process(std::array < int, 3 > { 1, 2, 3 }); }

طالما كان الصنف يدعم ‎std::get<I>(object)‎ و ‎std::tuple_size<T>::value‎، فيمكن توسيعه باستخدام الدالّة ‎process()‎ أعلاه، إذ أنّ الدالّة نفسها مستقلّة تمامًا عن عدد الوسائط.

إنشاء حزمة مُعاملات مُكوّنة من أعداد صحيحة

تُستخدَم std::integer_sequence لتخزين سلسلة من الأعداد الصحيحة التي يمكن تحويلها إلى حُزمة معاملات، وفائدتها الرئيسيّة هو إمكانية إنشاء قوالب الأصناف المصنعيّة التي ستنشئ تلك التسلسلات:

#include <iostream> #include <initializer_list> #include <utility> template < typename T, T...I > void print_sequence(std::integer_sequence < T, I... > ) { std::initializer_list < bool > { bool(std::cout << I << ' ')... }; std::cout << '\n'; } template < int Offset, typename T, T...I > void print_offset_sequence(std::integer_sequence < T, I... > ) { print_sequence(std::integer_sequence < T, T(I + Offset)... > ()); } int main() { // تحديد التسلسلات بشكل صريح print_sequence(std::integer_sequence < int, 1, 2, 3 > ()); print_sequence(std::integer_sequence < char, 'f', 'o', 'o' > ()); // توليد التسلسلات print_sequence(std::make_index_sequence < 10 > ()); print_sequence(std::make_integer_sequence < short, 10 > ()); print_offset_sequence < 'A' > (std::make_integer_sequence < char, 26 > ()); }

يَستخدم قالب الدّالة ‎print_sequence()‎ قائمة التهيئة ‎std::initializer_list<bool>‎ عند توسيع تسلسل الأعداد الصحيحة لضمان ترتيب التقييم، وتجنّب إنشاء متغيّر [مصفوفة] غير مستخدم.

تحويل سلسلة من الفهارس إلى نُسخ من عنصر ما

يؤدي توسيع حزمة معاملات من الفهارس في تعبير فاصلة (comma expression) يحمل قيمةً ما، إلى إنشاء نسخة من القيمة المقابلة لكل فهرس. ويرى المُصرِّفان ‎gcc‎ و ‎clang‎ أنّ الفهرس ليس له أيّ تأثير، لذا يطلقان تحذيرًا بشأنه (يمكن إسكات ‎gcc‎ عبر تحويل الفهرس إلى قيمة فارغة ‎void‎):

#include <algorithm> #include <array> #include <iostream> #include <iterator> #include <string> #include <utility> template < typename T, std::size_t...I > std::array < T, sizeof...(I) > make_array(T const & value, std::index_sequence < I... > ) { return std::array < T, sizeof...(I) > { (I, value)... }; } template < int N, typename T > std::array < T, N > make_array(T const & value) { return make_array(value, std::make_index_sequence < N > ()); } int main() { auto array = make_array < 20 > (std::string("value")); std::copy(array.begin(), array.end(), std::ostream_iterator < std::string > (std::cout, " ")); std::cout << "\n"; }

هذا الدرس جزء من سلسلة مقالات عن C++‎.

ترجمة -بتصرّف- للفصول 51 وحتى 60 من كتاب C++ Notes for Professionals

27,057 انستجرام احتفظت بمنشورات ورسائل محذوفة لأكثر من سنة

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

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

هذا يعني أن المحتوى الذي حذفه المستخدم منذ أكثر من سنة مازال موجوداً على مخدمات انستجرام.

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

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

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

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

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

المصدر:

TechCrunch

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

27,036 العدسات المتعددة البؤر.. وداعاً لقصر النظر عند الأطفال! عادة ما ينصح بالعدسات المتعددة البؤر للبالغين اعتباراً من سن الأربعين. دراسة على ما يقارب 300 طفل استمرت ثلاث سنوات خرجت بنتيجة أن الأطفال بين السابعة والحادية عشر يمكن أن يستفيدوا أيضاً من تلك العدسات. كيف ذلك؟
27,035 باحث ألماني-أمريكي يعرض "بخاخ" أنف مضاد لفيروس كورونا طور فريق بحثي أمريكي بخاخ أنف وقدموه على موقع علمي على أنه يستطيع إبطال مفعول فيروس كورونا في الأنف قبل تمكن الأخير من الدخول إلى الرئتين. ما هي آلية عمل البخاخ؟ وكم يستمر مفعوله؟
27,034 ريلمي تعلن عن باور بانك 30W Dart Charge للشحن بقدرة 30 وات

هذا المقال تم نشره بواسطة صدى التقنية صدى التقنية
رابط المقال ريلمي تعلن عن باور بانك 30W Dart Charge للشحن بقدرة 30 وات

أعلنت شركة ريلمي الصينية اليوم عن باور بانك أو بنك الطاقة المتنقل realme 30W Dart Charge بقدرة 30 وات، والذي يتيح لمستخدمي الهواتف الذكية والأجهزة المختلفة الشحن السريع في أثناء التنقل من خلال بطارية بقدرة 1000 ميللي أمبير/ساعة، ويتميز باور بانك ريلمي الجديد 30W Dart بتصميم نحيف وخفيف الوزن، مع دعم الحماية من خلال 15 طبقة متعددة، بالإضافة إلى وضع خاص لشحن الأجهزة الذكية القابلة للارتداء مثل سماعات الأذن وساعات اليد الذكية. 

وهذه أفضل مميزات باور بانك ريلمي الجديد:
  • الشحن السريع بقدرة 30 وات.
  • الشحن العكسي السريع بقدرة 30 وات.
  • وجود منفذين أحدهما منفذ USB-A والآخر منفذ USB-C.
  • تصميم مميز من ألياف الكربون باللونين الأسود والأصفر.
  • دعم تقنيات الشحن السريع المختلفة مثل VOOC وSuperVOOC.
  • دعم وضع الشحن للتيار المنخفض المناسب للأجهزة الذكية والملحقات.

ويأتي باور بانك ريلمي realme 30W Dart Charge الجديد بتصميم من ألياف الكربون على هيئة نسيج باللونين الأسود والأصفر بحواف منحنية تسهل حمله في أثناء التنقل، وتقول ريلمي أن التصميم مقاوم لبصمات الأصابع والخدوش والتآكل أو الأكسدة، وفيما يبلغ وزن الباور بانك أو بنك الطاقة حوالي 230 جرام، يبلغ سُمكه 17 ملم. 

ويحمل باور بانك ريلمي الجديد بطاريتين ليثيوم-آيون Li-ion بقدرة 1000 ميللي أمبير/ساعة، مثل الموجودة في الهواتف الذكية والحواسب اللوحية، وهو ما تقول ريلمي أنه يجعله مسموح للاستخدام في أثناء الطيران أو في رحلات الطيران.

إمكانية شحن الباور بانك خلال 96 دقيقة فقط

كما يأتي باور بانك ريلمي الجديد realme 30W Dart Charge مزودا بمنفذ USB-A ومنفذ USB-C، وفيما يتيح منفذ USB-A شحن الأجهزة الأخرى بقدرة 30 وات، يتيح منفذ USB-C شحن الأجهزة الأخرى أيضا بقدرة 30 وات، بالإضافة إلى الشحن العكسي أو شحن الباور بانك نفسه سريعا بقدرة 30 وات، مع وجود إضاءة LED للتعرف على مقدار الشحن وزر للتشغيل، مع إمكانية شحن بطارية الباور بانك كاملة خلال 96 دقيقة. 

التوافق مع تقنيات الشحن السريع المختلفة

وأكدت ريلمي أن البارو بانك الجديد بقدرة 30 وات وقدرة 1000 ميللي أمبير/ساعة متوافق مع العديد من تقنيات الشحن السريع، حيث يدعم بالإضافة إلى تقنية Dart Charge، كل من تقنيات الشحن السريع VOOC وSuperVOOC وQuick Charg (من كوالكوم) وUSB Power Delivery والعديد من التقنيات الأخرى، وهو ما تقول أنه يتيح شحن بطارية ريلمي 6 حتى 65 في المئة خلال 30 دقيقة.

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

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

هذا المقال تم نشره بواسطة صدى التقنية صدى التقنية
رابط المقال ريلمي تعلن عن باور بانك 30W Dart Charge للشحن بقدرة 30 وات

27,033 إزالة لعبة فورت نايت من متجري جوجل بلاي و App Store

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

تم إزالة اللعبة أولاً من متجر تطبيقات آبل، وسرعان ما تبعتها جوجل، وتستند الشركتين إلى أن ما فعلته فورت نايت يعدّ انتهاكاً لبنود الاستخدام الخاصة بها.

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

وكانت شركة Epic Games المطورة للعبة قد أطلقت متجر خاص باللعبة على منصة الحواسب الشخصية وتفرض نسبة 12% فقط، ووفرت لعبتها الشهيرة أولاً عبر موقعها مباشرة بحيث تذهب مدفوعات اللاعبين للشركة بالكامل، وبعدها توفرت على متجر جوجل بلاي.

لم تمرّ الأمور بسلام على Epic Games المطورة للعبة والتي أعلنت عن رفع دعوى قضائية ضد شركة آبل تتهمها فيها بالاحتكار الكامل على سوق المدفوعات للتطبيقات في نظام iOS.

يذكر أن Epic Games تحقق 1.8 مليار دولار من لعبتها التي لديها 350 مليون مستخدم مسجل.

المصدر:

Epic Games

التدوينة إزالة لعبة فورت نايت من متجري جوجل بلاي و App Store ظهرت أولاً على عالم التقنية.

27,019 الحرب تتواصل بين المنصات الاجتماعية وإدارة ترمب

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

وتم رفع الدعوى في شهر يونيو بواسطة مركز الديمقراطية والتكنولوجيا (CDT)، وهي مجموعة تكنولوجية مقرها واشنطن تمولها شركات فيسبوك وغوغل وتويتر.

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

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

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

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

وتعكس الدعوى القضائية توترات طويلة الأمد بين إدارة ترمب والمنصات الاجتماعية التي أصبحت أدوات رئيسية في ترسانة ترمب السياسية.

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

وتوضح دعوى المركز أن البيت الأبيض خالف التعديل الأول، الذي يحظر على المسؤولين الحكوميين الانتقام من فرد أو كيان.

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

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

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

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

27,016 آبل تحذف لعبة فورتنايت من متجرها للتطبيقات

أزالت شركة آبل لعبة فورتنايت (Fortnite) الخاصة بشركة (Epic Games) من متجرها للتطبيقات بعد أن أدخلت الشركة المطورة للعبة نظامها الخاص للدفع داخل التطبيق، والذي يتجاوز رسوم آبل القياسية البالغة 30 في المئة.

ويمثل هذا القرار تصعيدًا كبيرًا في الخلاف بين (Epic Games) وواحد من أكثر متاجر تطبيقات الأجهزة المحمولة هيمنة في العالم.

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

ونفذت (Epic Games) نظام الدفع الخاص بها في إصدار أندرويد من فورتنايت أيضًا، لكن شركة جوجل لم تتخذ بعد أي إجراء.

وقالت شركة آبل في بيان: إنها تخطط للعمل مع (Epic Games) لحل هذه الانتهاكات حتى تتمكن من إعادة (Fortnite) إلى (App Store)، لكنها لا تنوي منح (Epic Games) وضعًا خاصًا.

وأضافت "اتخذت (Epic Games) اليوم خطوة مؤسفة عبر انتهاكها لإرشادات (App Store) المصممة للحفاظ على المتجر آمنًا لمستخدمينا، ونتيجة لذلك، تمت إزالة تطبيق فورتنايت من المتجر، وقامت (Epic Games) بتمكين ميزة في تطبيقها لم تتم مراجعتها أو الموافقة عليها بواسطة آبل، وقد فعلت ذلك بقصد صريح لانتهاك إرشادات متجر التطبيقات فيما يتعلق بالمدفوعات داخل التطبيق".

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

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

ولطالما اشتكى تيم سويني (Tim Sweeney)، الرئيس التنفيذي لشركة (Epic Games)، من رسوم متاجر التطبيقات، ودعا إلى إجراء تغييرات جوهرية في كيفية إدارة شركات، مثل آبل وجوجل، للأعمال التجارية مع المطورين الخارجيين.

وتواجه شركة آبل انتقادات شديدة هذه الأيام بشأن كيفية إدارتها لمتجر (App Store) ورسومه الإلزامية، إلى جانب كيفية تطبيق إرشاداتها بطرق يشعر بعض المطورين بأنها غير عادلة وقد تكون في الواقع مصممة لإفادة آبل على منافسيها.

ومنذ ظهور (Fortnite) لأول مرة على الهاتف المحمول في 2018، كانت اللعبة موجودة كتطبيق (iOS) قياسي، وقال سويني علانية: إن شركته فعلت ذلك فقط لأنه لا توجد طريقة أخرى لدخول النظام البيئي المغلق لشركة آبل.

ويعني هذا أن آبل قد استحوذت على 30 في المئة من جميع عمليات الشراء داخل التطبيق لعملة (Fortnite) المستخدمة لشراء خدمة الاشتراك في (Battle Pass) والسلع الرقمية.

وكسبت (Fortnite) ما يصل إلى 2.4 مليار دولار في 2018 و1.8 مليار دولار في 2019 بسبب شعبيتها الكبرى عبر المنصات، حيث يمكن للاعبين استخدام الحساب نفسه عبر (iOS) و(Nintendo Switch) و(PS4) و(Xbox One) والحاسب الشخصي.

الصفحات