هل يحتوي D على شيء أقرب إلى C ++ 0x's Move Denantics؟
-
25-09-2019 - |
سؤال
مشكلة "أنواع القيمة" مع الموارد الخارجية (مثل std::vector<T>
أو std::string
) هو أن نسخها يميل إلى أن يكون مكلفًا للغاية ، ويتم إنشاء نسخ ضمنيًا في سياقات مختلفة ، لذلك يميل هذا إلى أن يكون مصدر قلق للأداء. إجابة C ++ 0x على هذه المشكلة نقل الدلالات, ، الذي يعتمد بشكل مفهوم على فكرة تبرئة الموارد وتشغيله تقنيًا المراجع RVALUE.
هل لدى D أي شيء مشابه لنقل الدلالات أو المراجع RValue؟
المحلول
أعتقد أن هناك عدة أماكن في D (مثل الهياكل العائدة) التي تمكنت من تحقيقها في حين أن C ++ سيجعلها نسخة. IIRC ، سيقوم برنامج التحويل البرمجي بحركة بدلاً من نسخة في أي حال من الأحوال حيث يمكن أن تحدد أن النسخة غير مطلوبة ، لذلك سيحدث نسخ الهيكل أقل في D من C ++. وبالطبع ، نظرًا لأن الفصول الدراسية هي مراجع ، فهي لا تواجه مشكلة على الإطلاق.
ولكن بغض النظر ، فإن Copy Construction يعمل بالفعل بشكل مختلف في D عن C ++. بشكل عام ، بدلاً من الإعلان عن مُنشئ النسخ ، تعلن عن مُنشئ ما بعد البليت: this(this)
. إنها تفعل memcpy كاملة من قبل this(this)
يُطلق عليه ، ولا تجري سوى أي تغييرات ضرورية للتأكد من أن البنية الجديدة منفصلة عن الأصل (مثل القيام بنسخة عميقة من متغيرات الأعضاء عند الحاجة) ، بدلاً من إنشاء مُنشئ جديد تمامًا يجب أن ينسخ كل شيء. لذلك ، فإن النهج العام يختلف بالفعل بعض الشيء عن C ++. من المتفق عليه عمومًا أنه لا ينبغي أن يكون للهياكل منشئات ما بعد البليت باهظة الثمن - يجب أن تكون بنيات نسخ رخيصة - لذلك فهي أقل من مشكلة مما ستكون عليه في C ++. الكائنات التي ستكون باهظة الثمن للنسخ هي عمومًا فئات أو هياكل مع مرجعية أو دلالات بقرة.
الحاويات عبارة عن أنواع مرجعية عمومًا (في Phobos ، هي هياكل بدلاً من الفصول الدراسية ، لأنها لا تحتاج إلى تعدد الأشكال ، لكن نسخها لا ينسخ محتوياتها ، لذلك لا تزال أنواع مرجعية) ، لذا فإن نسخها غير مكلف كما سيكون في C ++.
قد تكون هناك حالات في D حيث يمكن أن تستخدم شيئًا مشابهًا لمؤسسة Move ، ولكن بشكل عام ، تم تصميم D بطريقة تقلل من المشكلات التي يواجهها C ++ مع نسخ الكائنات حولها ، لذلك ليس في أي مكان بالقرب من المشكلة أنه في C ++.
نصائح أخرى
D لها قيمة منفصلة ودلالات الكائنات:
- إذا أعلنت نوعك كما
struct
, ، سيكون له قيمة الدلالي بشكل افتراضي - إذا أعلنت نوعك كما
class
, ، سيكون لها كائن الدلالي.
الآن ، على افتراض أنك لا تدير الذاكرة بنفسك ، لأنها الحالة الافتراضية في D - باستخدام جامع القمامة - عليك أن تفهم هذا الكائن من الأنواع المعلنة باسم class
هي مؤشرات تلقائي (أو "مرجع" إذا كنت تفضل) للكائن الحقيقي ، وليس الكائن الحقيقي نفسه.
لذلك ، عند تمرير المتجهات حول D ، ما تمريره هو المرجع/المؤشر. تلقائيا. لا توجد نسخة متورطة (بخلاف نسخة المرجع).
لهذا السبب لا "تحتاج" D ، C#، Java وغيرها من اللغة الدلالية (لأن معظم الأنواع هي الكائن الدلالي ويتم معالجتها بالرجوع إليها ، وليس عن طريق النسخة).
ربما يمكنهم تنفيذه ، لست متأكدًا. ولكن هل سيحصلون حقًا على زيادة الأداء كما في C ++؟ بطبيعتها ، لا يبدو من المحتمل.
لدي بطريقة ما أن الشعور بأن المراجع RVALUE والمفهوم الكامل لـ "Move Disantics" هو نتيجة أن يكون من الطبيعي في C ++ إنشاء كائنات محلية "مؤقتة". في لغات D ومعظم لغات GC ، من الأكثر شيوعًا أن يكون لديك كائنات على الكومة ، ثم لا يوجد أي عام مع نسخ كائن مؤقت (أو نقل) عدة مرات عند إرجاعه من خلال مكدس مكالمة - لذلك ليست هناك حاجة لآلية لتجنب تلك النفقات العامة أيضًا.
في D (ومعظم لغات GC) أ class
لا يتم نسخ الكائن أبدًا بشكل ضمني وأنت تمر فقط بالمرجع في معظم الوقت ، لذلك هذا مايو يعني أنك لا تحتاج إلى أي مراجع RValue لهم.
otoh ، struct
ليس من المفترض أن تكون الكائنات "مقابضًا للموارد" ، ولكن أنواع القيمة البسيطة التي تتصرف على غرار الأنواع المبنية - لذلك مرة أخرى ، لا يوجد سبب لأي خطوة دلالات هنا ، IMHO.
هذا من شأنه أن ينتج عنه استنتاج - لا يحتوي D على RValue Refs لأنه لا يحتاج إليها.
ومع ذلك ، لم أستخدم مراجع RVALUE في الممارسة العملية ، لم أقرأها فقط ، لذلك ربما تكون قد تخطيت بعض حالات الاستخدام الفعلي لهذه الميزة. يرجى التعامل مع هذا المنشور باعتباره مجموعة من الأفكار حول هذا الموضوع والتي نأمل أن تكون مفيدة لك ، وليس كحكمة موثوقة.
أعتقد أن جميع الإجابات فشلت تمامًا في الإجابة على السؤال الأصلي.
أولاً ، كما هو مذكور أعلاه ، يكون السؤال ذا صلة فقط بالهياكل. الفصول ليس لها خطوة ذات مغزى. كما ذكر أعلاه ، بالنسبة للهياكل ، سيحدث كمية معينة من التحرك تلقائيًا بواسطة المترجم في ظل ظروف معينة.
إذا كنت ترغب في السيطرة على عمليات الحركة ، فإليك ما عليك القيام به. يمكنك تعطيل النسخ عن طريق التعليق على هذا (هذا) مع disable. بعد ذلك ، يمكنك تجاوز C ++ constructor(constructor &&that)
عن طريق التعريف this(Struct that)
. وبالمثل ، يمكنك تجاوز التعيين مع opAssign(Struct that)
. في كلتا الحالتين ، تحتاج إلى التأكد من تدمير قيم that
.
للواجب ، نظرًا لأنك تحتاج أيضًا إلى تدمير القيمة القديمة this
, ، أبسط طريقة هي مبادلة لهم. تنفيذ C ++ unique_ptr
لذلك ، سوف تبدو مثل هذا:
struct UniquePtr(T) {
private T* ptr = null;
@disable this(this); // This disables both copy construction and opAssign
// The obvious constructor, destructor and accessor
this(T* ptr) {
if(ptr !is null)
this.ptr = ptr;
}
~this() {
freeMemory(ptr);
}
inout(T)* get() inout {
return ptr;
}
// Move operations
this(UniquePtr!T that) {
this.ptr = that.ptr;
that.ptr = null;
}
ref UniquePtr!T opAssign(UniquePtr!T that) { // Notice no "ref" on "that"
swap(this.ptr, that.ptr); // We change it anyways, because it's a temporary
return this;
}
}
تحرير: لاحظ أنني لم أحدد opAssign(ref UniquePtr!T that)
. هذا هو مشغل تعيين النسخ ، وإذا حاولت تحديده ، فسيخطئ المترجم لأنك أعلنت ، في @disable
خط ، أنه ليس لديك شيء من هذا القبيل.
أعتقد أنه إذا كنت بحاجة إلى المصدر لفقدان المورد الذي قد تكون في ورطة. ومع ذلك ، يمكنك في كثير من الأحيان تجنب الحاجة إلى القلق بشأن العديد من المالكين ، لذلك قد لا تكون مشكلة في معظم الحالات.