
من المعروف أن دلالات التهيئة هي واحدة من أكثر الأجزاء تعقيدًا في C ++. هناك العديد من أنواع التهيئة الموصوفة من خلال بناء جملة مختلف ، وتتفاعل جميعها بطريقة معقدة وصعبة. أحضر C ++ 11 مفهوم "التهيئة الشاملة". لسوء الحظ ، قدمت قواعد أكثر تعقيدًا ، وبالتالي تم حظرها في C ++ 14 و C ++ 17 وتغييرها مرة أخرى في C ++ 20.
تحت قص - فيديو وترجمة تقرير تيمور دوملر من مؤتمر C ++ في روسيا . يلخص Timur أولاً النتائج التاريخية لتطور التهيئة في C ++ ، ويقدم نظرة عامة منهجية على الإصدار الحالي من قاعدة التهيئة ، والمشكلات والمفاجآت النموذجية ، ويشرح كيفية استخدام كل هذه القواعد بفعالية ، وأخيراً يتحدث عن المقترحات الجديدة في المعيار التي يمكن أن تجعل دلالات التهيئة C ++ 20 أكثر ملاءمة قليلاً. كذلك القصة هي نيابة عنه.
جدول المحتويات

إن صورة GIF التي تراها الآن تنقل الرسالة الرئيسية للتقرير جيدًا. لقد عثرت عليه على الإنترنت قبل حوالي ستة أشهر ، ونشرته على Twitter. في تعليقات لها ، قال أحدهم أن هناك ثلاثة أنواع أخرى من التهيئة مفقودة. بدأت مناقشة ، دعيت خلالها لتقديم تقرير عن ذلك. وهكذا بدأ كل شيء.
حول التهيئة نيكولاي Yossutis قال بالفعل. تضمن تقريره قائمة بسرد 19 طريقة مختلفة لتهيئة int:
int i1;
يبدو لي أن هذا وضع فريد بالنسبة للغة البرمجة. تعد تهيئة المتغير أحد أبسط الإجراءات ، ولكن في C ++ ، ليس من السهل على الإطلاق القيام به. من غير المحتمل أن تحتوي هذه اللغة على أي مجال آخر حيث كان في السنوات الأخيرة كان هناك العديد من التقارير عن الانحرافات عن المعايير والتصحيحات والتغييرات. تتغير قواعد التهيئة من قياسي إلى قياسي ، وهناك عدد لا يحصى من المنشورات على الإنترنت حول كيفية التهيئة في C ++. لذلك ، لإجراء مراجعة منهجية هي مهمة غير تافهة.
سوف أقدم المادة بترتيب زمني: أولاً سنتحدث عن ما تم توريثه من C ، ثم عن C ++ 98 ، ثم عن C ++ 03 و C ++ 11 و C ++ 14 و C ++ 17. سنناقش الأخطاء الشائعة ، وسأقدم توصياتي بشأن التهيئة المناسبة. سأتحدث أيضًا عن الابتكارات في C ++ 20. سيتم تقديم جدول نظرة عامة في نهاية التقرير.
التهيئة الافتراضية (C)
في C ++ ، يتم توريث الكثير من الأشياء من C ، ولهذا السبب سنبدأ بها. هناك عدة طرق لتهيئة المتغيرات في C. قد لا تتم تهيئة على الإطلاق ، وهذا ما يسمى التهيئة الافتراضية . في رأيي ، هذا اسم مؤسف. الحقيقة هي أنه لم يتم تعيين قيمة افتراضية للمتغير ، فهي ببساطة غير مهيأة. إذا تحولت إلى متغير غير مهيأ في C ++ و C ، فإنك تحصل على سلوك غير محدد:
int main() { int i; return i;
ينطبق الشيء نفسه على الأنواع المخصصة: إذا كان هناك بعض الحقول غير المهيأة في بعض struct
، فعند الوصول إليها ، يحدث سلوك غير محدد أيضًا:
struct Widget { int i; int j; }; int main() { Widget widget; return widget.i;
تمت إضافة العديد من التراكيب الجديدة إلى C ++: الفئات والمنشئين والأساليب العامة والخاصة ، ولكن لا يؤثر أي من ذلك على السلوك الموصوف للتو. إذا لم تتم تهيئة عنصر ما في الفصل ، فعند الوصول إليه ، يحدث سلوك غير محدد:
class Widget { public: Widget() {} int get_i() const noexcept { return i; } int get_j() const noexcept { return j; } private: int i; int j; }; int main() { Widget widget; return widget.get_i();
لا توجد طريقة سحرية لتهيئة عنصر فئة في C ++ افتراضيًا. هذه نقطة مثيرة للاهتمام ، وخلال السنوات القليلة الأولى من حياتي المهنية مع C ++ ، لم أكن أعرف ذلك. لم يذكرني المترجم ولا IDE الذي استخدمته في ذلك الوقت بأي طريقة. لم يهتم زملائي بهذه الميزة عند التحقق من الرمز. أنا متأكد من أنه بسببها ، هناك بعض الأخطاء الغريبة في شفرتي المكتوبة خلال هذه السنوات. بدا لي واضحًا أنه يجب على الطبقات تهيئة متغيراتها.
في C ++ 98 ، يمكنك تهيئة المتغيرات باستخدام قائمة مهيئ الأعضاء. لكن مثل هذا الحل للمشكلة ليس هو الأمثل ، لأنه يجب أن يتم في كل مُنشئ ، وهذا سهل النسيان. بالإضافة إلى ذلك ، تتم عملية التهيئة بالترتيب الذي يتم به الإعلان عن المتغيرات وليس بترتيب قائمة مُهيئ الأعضاء:
في C ++ 11 ، تمت إضافة أدوات تهيئة الأعضاء المباشرة ، والتي هي أكثر ملاءمة للاستخدام. إنها تتيح لك تهيئة جميع المتغيرات في نفس الوقت ، وهذا يعطي الثقة في تهيئة جميع العناصر:
توصيتي الأولى: كلما استطعت ، استخدم دائمًا DMI (مُهيئات الأعضاء المباشرين). يمكن استخدامها مع أنواع مدمجة ( float
و int
) ، ومع كائنات. عادة تهيئة العناصر تجعلنا نتعامل مع هذه القضية بوعي أكبر.
نسخ التهيئة (C)
لذلك ، فإن طريقة التهيئة الأولى الموروثة من C هي التهيئة بشكل افتراضي ، ويجب عدم استخدامها. الطريقة الثانية هي نسخة التهيئة . في هذه الحالة ، نشير إلى المتغير ومن خلال علامة المساواة - قيمته:
يتم استخدام تهيئة النسخ أيضًا عند تمرير وسيطة إلى دالة حسب القيمة ، أو عند إرجاع كائن من دالة حسب القيمة:
قد تعطي علامة المساواة الانطباع بأنه يتم تعيين قيمة ، لكن هذا ليس كذلك. نسخ التهيئة ليست مهمة قيمة. لن يكون هناك شيء حول الاعتمادات في هذا التقرير.
خاصية مهمة أخرى لتهيئة النسخ: إذا لم تتطابق أنواع القيم ، فسيتم تنفيذ تسلسل تحويل. يحتوي تسلسل التحويل على قواعد معينة ، على سبيل المثال ، لا يستدعي المنشئين الصريحين ، لأنهم لا يحولون المنشئات. لذلك ، إذا قمت بإجراء تهيئة النسخ لكائن تم تعليم مُنشئه على أنه صريح ، يحدث خطأ في التحويل البرمجي:
struct Widget { explicit Widget(int) {} }; Widget w1 = 1;
علاوة على ذلك ، إذا كان هناك مُنشئ آخر غير صريح ، ولكنه أسوأ في الكتابة ، فسوف يطلق عليه تهيئة النسخ ، متجاهلاً المُنشئ الصريح:
struct Widget { explicit Widget(int) {} Widget(double) {} }; Widget w1 = 1;
التهيئة الإجمالية (C)
النوع الثالث من التهيئة الذي أود التحدث عنه هو التهيئة الإجمالية . يتم تنفيذها عند تهيئة الصفيف مع سلسلة من القيم في الأقواس:
int i[4] = {0, 1, 2, 3};
إذا لم تحدد حجم المصفوفة ، فسيتم اشتقاقها من عدد القيم الموجودة بين قوسين:
int j[] = {0, 1, 2, 3};
يتم استخدام التهيئة نفسها للفئات التجميعية ، أي الفصول التي هي مجرد مجموعة من العناصر العامة (هناك عدد قليل من القواعد في تعريف الفصول التجميعية ، لكننا الآن لن نتناولها):
struct Widget { int i; float j; }; Widget widget = {1, 3.14159};
تم بناء الجملة هذا حتى في C و C ++ 98 ، وبدءًا من C ++ 11 ، يمكنك تخطي علامة المساواة في ذلك:
Widget widget{1, 3.14159};
التهيئة التجميعية تستخدم بالفعل تهيئة النسخ لكل عنصر. لذلك ، إذا حاولت استخدام التهيئة التجميعية (سواء بعلامة المساواة وبدونها) للعديد من الكائنات ذات التركيبات الصريحة ، فسيتم إجراء تهيئة النسخ لكل كائن ويحدث خطأ في التحويل البرمجي:
struct Widget { explicit Widget(int) {} }; struct Thingy { Widget w1, w2; }; int main() { Thingy thingy = {3, 4};
وإذا كان هناك مُنشئ آخر لهذه الكائنات ، غير صريح ، فسيتم استدعاؤه ، حتى إذا كان من الأسوأ الكتابة:
struct Widget { explicit Widget(int) {} Widget(double) {} }; struct Thingy { Widget w1, w2; }; int main() { Thingy thingy = {3, 4};
دعنا نفكر في خاصية واحدة من التهيئة الإجمالية. سؤال: ما قيمة هذا البرنامج؟
struct Widget { int i; int j; }; int main() { Widget widget = {1}; return widget.j; }
النص المخفيهذا صحيح ، صفر. إذا تخطيت بعض العناصر في صفيف من القيم أثناء التهيئة التجميعية ، فسيتم تعيين المتغيرات المقابلة على صفر. هذه خاصية مفيدة للغاية ، لأنه بفضلها لا يمكن أبدًا أن تكون هناك عناصر غير مهيأة. وهو يعمل مع الطبقات الإجمالية ومع المصفوفات:
خاصية هامة أخرى من التهيئة الإجمالية هي إغفال الأقواس (elision elision). ما هي القيمة التي تعتقد أن هذا البرنامج يعود؟ يحتوي على عنصر Widget
، وهو عبارة عن مجموع قيمتين int
، و Thingy
، وهو مجموع عنصر Widget
و int
. ما الذي نحصل عليه إذا قمنا بتمرير قيمتين التهيئة إليه: {1, 2}
؟
struct Widget { int i; int j; }; struct Thingy { Widget w; int k; }; int main() { Thingy t = {1, 2}; return tk;
النص المخفيالجواب هو صفر. نحن هنا نتعامل مع مجموعة فرعية ، أي فئة متداخلة متداخلة. يمكن تهيئة هذه الفئات باستخدام الأقواس المتداخلة ، ولكن يمكنك تخطي أحد أزواج الأقواس هذه. في هذه الحالة ، يتم تنفيذ اجتياز متكرر للمجموع الفرعي ، ويظهر {1, 2}
أنه يعادل {{1, 2}, 0}
. من المسلم به أن هذه الخاصية ليست واضحة تمامًا.
التهيئة الساكنة (C)
وأخيرًا ، يتم أيضًا تهيئة التهيئة الساكنة من C: يتم دائمًا تهيئة المتغيرات الثابتة. يمكن القيام بذلك بعدة طرق. يمكن تهيئة متغير ثابت بتعبير ثابت. في هذه الحالة ، يحدث التهيئة في وقت الترجمة. إذا لم تقم بتعيين أي قيمة للمتغير ، فسيتم تهيئتها إلى صفر:
static int i = 3;
إرجاع هذا البرنامج 3 على الرغم من عدم تهيئة j
. إذا تم تهيئة المتغير ليس بواسطة ثابت ، ولكن بواسطة كائن ، يمكن أن تنشأ مشاكل.
فيما يلي مثال من مكتبة حقيقية كنت أعمل عليها:
static Colour red = {255, 0, 0};
كان هناك فئة ألوان فيها ، وتم تعريف الألوان الأساسية (الأحمر والأخضر والأزرق) على أنها كائنات ثابتة. هذا إجراء صحيح ، ولكن بمجرد ظهور كائن ثابت آخر في المُهيئ الذي يستخدم red
، يظهر عدم اليقين لأنه لا يوجد ترتيب جامد يتم فيه تهيئة المتغيرات. يمكن للتطبيق الخاص بك الوصول إلى متغير غير مهيأ ، ثم تعطله. لحسن الحظ ، في C ++ 11 أصبح من الممكن استخدام مُنشئ constexpr
، ثم نتعامل مع التهيئة المستمرة. في هذه الحالة ، لا توجد مشاكل في ترتيب التهيئة.
لذلك ، يتم توريث أربعة أنواع من التهيئة من اللغة C: التهيئة الافتراضية والنسخ والتجميع والتهيئة الثابتة.
التهيئة المباشرة (C ++ 98)
دعنا ننتقل إلى C ++ 98. ولعل أهم ميزة تميز C ++ من C هي الصانعين. فيما يلي مثال لاستدعاء المنشئ:
Widget widget(1, 2); int(3);
باستخدام بناء الجملة نفسه ، يمكنك تهيئة أنواع مدمجة مثل int
و float
. يسمى بناء الجملة هذا التهيئة المباشرة . يتم تنفيذها دائمًا عندما يكون لدينا حجة بين قوسين.
بالنسبة للأنواع المضمنة ( int
، bool
، float
) ، لا يوجد اختلاف عن تهيئة النسخ هنا. إذا كنا نتحدث عن أنواع المستخدمين ، فعندئذ بخلاف تهيئة النسخ ، مع التهيئة المباشرة ، يمكنك تمرير العديد من الوسائط. في الواقع ، من أجل هذا ، اخترع التهيئة المباشرة.
بالإضافة إلى ذلك ، التهيئة المباشرة لا تنفذ تسلسل تحويل. بدلاً من ذلك ، يتم استدعاء المنشئ باستخدام دقة التحميل الزائد. التهيئة المباشرة لها نفس بناء جملة استدعاء دالة ، وتستخدم نفس المنطق مثل وظائف C ++ الأخرى.
لذلك ، في الموقف مع مُنشئ صريح ، التهيئة المباشرة يعمل بشكل جيد ، على الرغم من أن نسخة التهيئة يلقي خطأ:
struct Widget { explicit Widget(int) {} }; Widget w1 = 1;
في حالة وجود مُنشئين ، أحدهما واضح ، والثاني أقل ملاءمة من حيث النوع ، الأول يسمى بتهيئة مباشرة ، والثاني يسمى بالنسخة. في هذه الحالة ، سيؤدي تغيير بناء الجملة إلى استدعاء مُنشئ آخر - وغالبًا ما يتم نسيان هذا:
struct Widget { explicit Widget(int) {} Widget(double) {} }; Widget w1 = 1;
يتم استخدام التهيئة المباشرة دائمًا عند استخدام الأقواس ، بما في ذلك عند استخدام تدوين استدعاء المنشئ لتهيئة كائن مؤقت ، وكذلك في التعبيرات new
باستخدام مُهيئ بين قوسين وفي تعبيرات cast
:
useWidget(Widget(1, 2));
يوجد بناء الجملة هذا طالما أن C ++ نفسه موجود ، ولديه عيب مهم ذكره نيكولاي في عنوانه الرئيسي: التحليل الأكثر إثارة للحيرة . هذا يعني أن كل شيء يمكن للمترجم قراءته كإعلان (إعلان) ، فإنه يقرأ تمامًا كإعلان.
ضع في اعتبارك مثالًا يحتوي على فئة Widget
وفئة Thingy
، Thingy
يستقبل عنصر Widget
:
struct Widget {}; struct Thingy { Thingy(Widget) {} }; int main () { Thingy thingy(Widget()); }
للوهلة الأولى ، يبدو أنه عند تهيئة Thingy
، يتم تمرير Widget
الافتراضية التي تم إنشاؤها إليها ، ولكن في الواقع ، يتم الإعلان عن الوظيفة هنا. تعلن هذه التعليمة البرمجية عن وظيفة تستقبل وظيفة أخرى كمدخلات ، والتي لا تتلقى شيئًا كمدخلات وترجع عنصر Widget
، وتُرجع الدالة الأولى Thingy
. يتم تجميع الشفرة دون أخطاء ، لكن من غير المحتمل أن نسعى إلى مثل هذا السلوك.
تهيئة القيمة (C ++ 03)
دعنا ننتقل إلى الإصدار التالي - C ++ 03. من المقبول عمومًا عدم وجود تغييرات كبيرة في هذا الإصدار ، لكن هذا ليس كذلك. في C ++ 03 ، ظهرت تهيئة القيمة ، حيث تتم كتابة أقواس فارغة:
int main() { return int();
في C ++ 98 ، يحدث سلوك غير معرف هنا لأن التهيئة تتم بشكل افتراضي ، ويبدأ بـ C ++ 03 هذا البرنامج بإرجاع صفر.
والقاعدة هي: إذا كان هناك مُنشئ افتراضي مُعرَّف من قِبل المستخدم ، فإن التهيئة بقيمة تستدعي هذا المُنشئ ، وإلا يتم إرجاع الصفر.
ضع في اعتبارك الموقف مع المنشئ المخصص بمزيد من التفاصيل:
struct Widget { int i; }; Widget get_widget() { return Widget();
في هذا البرنامج ، تقوم الدالة بتهيئة قيمة Widget
الجديدة Widget
. نحن نسمي هذه الوظيفة والوصول إلى العنصر i
للكائن Widget
. منذ C ++ 03 ، القيمة المرجعة هنا هي صفر ، لأنه لا يوجد مُنشئ افتراضي المعرفة من قبل المستخدم. وإذا كان مثل هذا المنشئ موجودًا ، لكن لم يتم تهيئة i
، فإننا نحصل على سلوك غير محدد:
struct Widget { Widget() {}
تجدر الإشارة إلى أن "المعرفة من قبل المستخدم" لا تعني "المعرفة من قبل المستخدم". هذا يعني أنه يجب على المستخدم توفير نص المنشئ ، مثل الأقواس المعقوفة. في المثال أعلاه ، استبدل هيكل المُنشئ بـ = default
(تمت إضافة هذه الميزة في C ++ 11) ، يتغير معنى البرنامج. الآن لدينا مُنشئ مُعرَّف من قِبل المستخدم (مُعرَّف من قِبل المستخدم) ، ولكن لم يتم توفيره من قِبل المستخدم (مُقدّم من قِبل المستخدم) ، لذلك يُرجع البرنامج صفرًا:
struct Widget { Widget() = default;
الآن دعونا نحاول Widget() = default
خارج الفصل. تم تغيير معنى البرنامج مرة أخرى: يعتبر Widget() = default
منشئ مقدم من قبل المستخدم إذا كان خارج الفصل. إرجاع البرنامج سلوك غير محدد مرة أخرى.
struct Widget { Widget(); int i; }; Widget::Widget() = default;
هناك منطق معين: يمكن أن يكون المنشئ المحدد خارج الفصل داخل وحدة ترجمة أخرى. قد لا يرى المترجم هذا المنشئ ، حيث قد يكون في ملف .cpp
آخر. لذلك ، لا يمكن للمترجم استخلاص أي استنتاجات حول مُنشئ مثل ، ولا يمكنه التمييز بين مُنشئ ذو نص منشئ = default
.
التهيئة الشاملة (C ++ 11)
كان هناك العديد من التغييرات المهمة في C ++ 11. على وجه الخصوص ، تم تقديم التهيئة العامة (الموحدة) ، والتي أفضل أن أسميها "التهيئة أحادية القرن" لأنها ببساطة سحرية. دعونا نرى لماذا ظهرت.
كما لاحظت بالفعل ، في C ++ هناك الكثير من صيغ تهيئة مختلفة مع سلوكيات مختلفة. تسبب التحليل المحير بالأقواس في الكثير من الإزعاج. لم يعجب المطورون أيضًا أن التهيئة التجميعية يمكن استخدامها فقط مع المصفوفات ، ولكن ليس مع حاويات مثل std::vector
. بدلاً من ذلك ، كان عليك تنفيذ .reserve
و .push_back
، أو استخدام جميع أنواع المكتبات المخيفة:
حاول مبدعو اللغة حل كل هذه المشكلات من خلال تقديم بناء جملة بأقواس مجعدة ولكن بدون علامة مساوية. كان من المفترض أن هذا سيكون بناء جملة واحد لجميع الأنواع ، حيث يتم استخدام الأقواس المتعرجة وليس هناك مشكلة تحليل محيرة. في معظم الحالات ، يقوم بناء الجملة هذا بعمله.
يسمى هذا التهيئة الجديد تهيئة القائمة ، ويأتي في نوعين: مباشر ونسخ. في الحالة الأولى ، يتم استخدام الأقواس المتعرجة فقط ، في الحالة الثانية - الأقواس المتعرجة بعلامة مساوية:
تسمى القائمة المستخدمة في التهيئة braced-init-list . من المهم أن هذه القائمة ليست كائنًا ؛ وليس لها أي نوع. لا يؤدي التبديل إلى الإصدار C ++ 11 من الإصدارات السابقة إلى حدوث أي مشاكل مع الأنواع التجميعية ، وبالتالي فإن هذا التغيير ليس ضروريًا. ولكن الآن القائمة في الأقواس لديها ميزات جديدة. على الرغم من أنه لا يحتوي على نوع ، إلا أنه يمكن إخفاؤه إلى قائمة std::initializer_list
، إلا أنه نوع جديد خاص. وإذا كان هناك مُنشئ يقبل std::initializer_list
كمدخلات ، فسيتم تسمية هذا المُنشئ:
template <typename T> class vector {
يبدو لي أنه من جانب لجنة C ++ ، لم تكن std::initializer_list
هي الحل الأكثر نجاحًا. منه تضر أكثر مما تنفع.
بادئ ذي بدء ، std::initializer_list
هو ناقل ذو حجم ثابت مع عناصر const
. هذا هو ، إنه نوع ، له وظائف begin
end
التي يعيدها المتكررون ، وله نوع التكرار الخاص به ، وللاستخدام ، تحتاج إلى تضمين رأس خاص. نظرًا لأن عناصر std::initializer_list
هي const
، فلا يمكن نقلها ، لذلك إذا كانت T
في الكود أعلاه من النوع move-only ، فلن يتم تنفيذ الكود.
بعد ذلك ، std::initializer_list
هو كائن. باستخدامه ، في الواقع ، نقوم بإنشاء ونقل الكائنات. كقاعدة عامة ، يمكن للمترجم تحسين هذا ، ولكن من وجهة نظر الدلالات ، ما زلنا نتعامل مع الأشياء غير الضرورية.
قبل بضعة أشهر ، كان هناك استطلاع على Twitter: إذا أمكنك العودة في الوقت المناسب وإزالة شيء من C ++ ، فما الذي ستقوم بإزالته؟ حصل معظم الأصوات على initializer_list
بالضبط.
https://twitter.com/shafikyaghmour/status/1058031143935561728
, initializer_list
. , .
, . , initializer_list
, . :
std::vector<int> v(3, 0);
vector
int
, , , — . . , initializer_list
, 3 0.
:
std::string s(48, 'a');
48 «», «0». , string
initializer_list
. 48 , . ASCII 48 — «0». , , , int
char
. . , , .
. , ? ?
template <typename T, size_t N> auto test() { return std::vector<T>{N}; } int main () { return test<std::string, 3>().size(); }
, — 3. string
int
, 1, std::vector<std::int>
initializer_list
. initializer_list
, . string
int
float
, , . , . , emplace , . , {}
.
, .
.
— ( {a}
)
( = {a}
);
:
- «» ,
std::initializer_list
.
— . - ,
()
.
.
1: = {a}
, a
,
.
2: , {}
.
, initializer_list
.
Widget<int> widget{}\
?
template Typename<T> struct Widget { Widget(); Widget(std::initializer_list<T>); }; int main() { Widget<int> widget{};
, , initializer_list
, initializer_list
. . , , initializer_list
. , . , .
{}
. , -, , Widget() = default
Widget() {}
— .
Widget() = default
:
struct Widget { Widget() = default; int i; }; int main() { Widget widget{};
Widget() {}
:
struct Widget { Widget() {};
: , (narrowing conversions). int
double
, , :
int main() { int i{2.0};
, double
. C++11, , . :
struct Widget { int i; int j; }; int main() { Widget widget = {1.0, 0.0};
, , , , (brace elision). , , . , map
. map
, — :
std::map<std::string, std::int> my_map {{"abc", 0}, {"def", 1}};
, . :
std::vector<std::string> v1 {"abc", "def"};
, , initializer_list
. initializer_list
, , , . , . , .
initializer_list
— initializer_list
, . , const char*
. , string
, char
. . , , .
:
. braced-init-list . :
Widget<int> f1() { return {3, 0};
, , braced-init-list . braced-init-list , .
, . StackOverflow , . , . , , :
#include <iostream> struct A { A() {} A(const A&) {} }; struct B { B(const A&) {} }; void f(const A&) { std::cout << "A" << std::endl; } void f(const B&) { std::cout << "B" << std::endl; } int main() { A a; f( {a} ); // A f( {{a}} ); // ambiguous f( {{{a}}} ); // B f({{{{a}}}}); // no matching function }
++14
, C++11 . , , . C++14. , .
, ++11 direct member initializers, . , direct member initializers . ++14, direct member initializers:
struct Widget { int i = 0; int j = 0; }; Widget widget{1, 2};
, auto
. ++11 auto
braced-init-list, std::initializer_list
:
int i = 3;
: auto i{3}
, int
, std::initializer_list<int>
. ++14 , auto i{3}
int
. , . , auto i = {3}
std::initializer_list<int>
. , : int
, — initializer_list
.
auto i = 3;
, C++14 , , , , . , .
, ++14 :
, , std::initializer_list
.
std::initializer_list
move-only .
c , emplace
make_unique
.
, :
, , .
: assert(Widget(2,3))
, assert(Widget{2,3})
. , , , . , . .
C++
, ++.
int
, . . — , .
: , , std::initializer_list
, direct member initializers. , .
, é . .
struct Point { int x = 0; int y = 0; }; setPosition(Point{2, 3}); takeWidget(Widget{});
braced-init-list — .
setPosition({2, 3}); takeWidget({});
, , . , — , . , , , , , . , , initializer_list
. : , , .
:
= value
= {args}
= {}
:
std::initializer_list
- direct member initialisation (
(args)
)
{args}
{}
é
(args)
, (args)
vexing parse. . 2013 , , auto
. , : auto i;
— . , :
auto widget = Widget(2, 3);
, . , , vexing parse:
auto thingy = Thingy();
« auto» («almost always auto», AAA), ++11 ++14 , , , std::atomic<int>
:
auto count = std::atomic<int>(0);
, atomic . , , , , . ++17 , , (guaranteed copy elision):
auto count = std::atomic<int>(0);
auto
. — direct member initializers. auto
.
++17 CTAD (class template argument deduction). , . . , CppCon, CTAD , . , ++17 , ++11 ++14, , . , , , , .
(++20)
++20, . , , : (designated initialization):
struct Widget { int a; int b; int c; }; int main() { Widget widget{.a = 3, .c = 7}; };
, . , , . , .
, b
.
, , , . , .
, , 99, :
, , . ++ , , . :
Widget widget{.c = 7, .a = 3};
, .
++ , {.ce = 7};
, {.c{.e = 7}}
:
Widget widget{.ce = 7};
++ , , :
Widget widget{.a = 3, 7};
++ . , -, , .
int arr[3]{.[1] = 7};
C++20
++20 , . ( wg21.link/p1008 ).
++17 , , . , , , :
struct Widget { Widget() = delete; int i; int j; }; Widget widget1;
, , . ++20 . , . , . , , , .
( wg21.link/p1009 ). Braced-init-list new
, : , ? — , : braced-init-list new
:
double a[]{1, 2, 3};
, ++11 braced-init-list. ++ . , .
(C++20)
, ++20 . , . ++20 : ( wg21.link/p0960 ).
struct Widget { int i; int j; }; Widget widget(1, 2);
. , emplace
make_unique
. . : auto
, : 58.11 .
struct Widget { int i; int j; }; auto widget = Widget(1, 2);
, :
int arr[3](0, 1, 2);
, : uniform 2.0. . , , , , . — initializer_list
: , , — . , . , - , — . .
, . direct member initializers. auto
. direct member initializers — , . , . — , .
, , . — , — . , .

, , C++ Russia 2019 Piter «Type punning in modern C++» . , ++20, , , «» ++ , .