سؤال

ماذا يكون ال explicit الكلمة الأساسية تعني في C++؟

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

المحلول

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

فيما يلي مثال لفئة مع مُنشئ يمكن استخدامه للتحويلات الضمنية:

class Foo
{
public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo) : m_foo (foo) 
  {
  }

  int GetFoo () { return m_foo; }

private:
  int m_foo;
};

إليك وظيفة بسيطة تستغرق Foo هدف:

void DoBar (Foo foo)
{
  int i = foo.GetFoo ();
}

وهنا حيث DoBar تسمى الدالة .

int main ()
{
  DoBar (42);
}

الحجة ليست أ Foo كائن، ولكن int.ومع ذلك، هناك منشئ ل Foo الذي يأخذ int لذلك يمكن استخدام هذا المنشئ لتحويل المعلمة إلى النوع الصحيح.

يُسمح للمترجم بالقيام بذلك مرة واحدة لكل معلمة.

البادئة explicit الكلمة الأساسية للمنشئ تمنع المترجم من استخدام هذا المنشئ للتحويلات الضمنية.ستؤدي إضافتها إلى الفئة أعلاه إلى إنشاء خطأ في برنامج التحويل البرمجي عند استدعاء الوظيفة DoBar (42).من الضروري الآن الدعوة للتحويل بشكل صريح مع DoBar (Foo (42))

السبب وراء رغبتك في القيام بذلك هو تجنب البناء العرضي الذي يمكن أن يخفي الأخطاء.مثال مفتعل:

  • لديك MyString(int size) فئة مع مُنشئ يقوم بإنشاء سلسلة بالحجم المحدد.لديك وظيفة print(const MyString&), ، وتتصل print(3) (عندما انت في الحقيقة يقصد الاتصال print("3")).تتوقع أن يقوم بطباعة "3"، لكنه يطبع سلسلة فارغة بطول 3 بدلاً من ذلك.

نصائح أخرى

لنفترض أن لديك فئة String:

class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

الآن، إذا حاولت:

String mystring = 'x';

الشخصية 'x' سيتم تحويلها ضمنيا إلى int ومن ثم String(int) سيتم استدعاء المنشئ.لكن هذا ليس ما قد يقصده المستخدم.لذلك، لمنع مثل هذه الظروف، يجب علينا تعريف المنشئ بأنه explicit:

class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};

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

على سبيل المثال، إذا كان لديك فئة سلسلة مع مُنشئ String(const char* s), ، ربما هذا هو بالضبط ما تريده.يمكنك تمرير أ const char* إلى وظيفة تتوقع أ String, ، وسيقوم المترجم تلقائيًا بإنشاء ملف مؤقت String كائن بالنسبة لك.

من ناحية أخرى، إذا كان لديك فئة عازلة منشئها Buffer(int size) حجم المخزن المؤقت بالبايت، فمن المحتمل أنك لا تريد أن يعمل المترجم بهدوء intفي Bufferس.لمنع ذلك، عليك أن تعلن المنشئ مع explicit الكلمة الرئيسية:

class Buffer { explicit Buffer(int size); ... }

من ذلك الطريق،

void useBuffer(Buffer& buf);
useBuffer(4);

يصبح خطأ وقت الترجمة.إذا كنت ترغب في تمرير مؤقت Buffer كائن، عليك أن تفعل ذلك بشكل صريح:

useBuffer(Buffer(4));

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

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

خذ بعين الاعتبار الفئة التالية بدون منشئ صريح:

class Foo
{
public:
    Foo(int x) : m_x(x)
    {
    }

private:
    int m_x;
};

يمكن إنشاء كائنات الفئة Foo بطريقتين:

Foo bar1(10);

Foo bar2 = 20;

اعتمادًا على التنفيذ، قد تكون الطريقة الثانية لإنشاء مثيل للفئة Foo مربكة، أو ليست ما قصده المبرمج.البادئة explicit الكلمة الرئيسية للمنشئ ستؤدي إلى حدوث خطأ في المحول البرمجي عند Foo bar2 = 20;.

إنها عادة ممارسة جيدة للإعلان عن مُنشئات ذات وسيطة واحدة explicit, ، ما لم يحظر التنفيذ الخاص بك ذلك على وجه التحديد.

لاحظ أيضًا أن المنشئين مع

  • الوسائط الافتراضية لجميع المعلمات، أو
  • الوسائط الافتراضية للمعلمة الثانية فصاعدًا

يمكن استخدام كلاهما كمنشئات ذات وسيطة واحدة.لذلك قد ترغب في عمل هذه أيضًا explicit.

مثال عندما كنت عمدا لا تريد أن تجعل مُنشئ الوسيطة الفردية الخاص بك واضحًا إذا كنت تقوم بإنشاء عامل (انظر إلى البنية "add_x" المعلنة في هذا إجابة).في مثل هذه الحالة، إنشاء كائن مثل add_x add30 = 30; من المحتمل أن يكون له معنى.

هنا هي كتابة جيدة على المنشئين الصريحين.

ال explicit تقوم الكلمة الأساسية بإنشاء مُنشئ تحويل إلى مُنشئ غير تحويل.ونتيجة لذلك، فإن التعليمات البرمجية تكون أقل عرضة للخطأ.

الكلمة الرئيسية explicit يرافق سواء

  • مُنشئ من الفئة X لا يمكن استخدامه لتحويل المعلمة الأولى (أي فقط) ضمنيًا إلى النوع X

C++ [class.conv.ctor]

1) يحدد المُنشئ المعلن بدون محدد الوظيفة الصريح التحويل من أنواع معلماته إلى نوع فئته.يسمى هذا المُنشئ مُنشئ التحويل.

2) يبني المنشئ الصريح الكائنات تمامًا مثل المنشئات غير الصريحة، ولكنه يفعل ذلك فقط عندما يتم استخدام صيغة التهيئة المباشرة (8.5) أو حيث يتم استخدام القوالب (5.2.9، 5.4) بشكل صريح.قد يكون المُنشئ الافتراضي مُنشئًا صريحًا؛سيتم استخدام هذا المُنشئ لأداء التخصيص الافتراضي أو القيمة (8.5).

  • أو وظيفة تحويل يتم اعتبارها فقط للتهيئة المباشرة والتحويل الصريح.

C++ [class.conv.fct]

2) قد تكون وظيفة التحويل صريحة (7.1.2)، وفي هذه الحالة تعتبر فقط بمثابة تحويل محدد من قبل المستخدم للتهيئة المباشرة (8.5).خلاف ذلك ، لا تقتصر التحويلات المعرفة من قبل المستخدم على الاستخدام في المهام والتهيئة.

ملخص

لا يمكن استخدام وظائف التحويل الصريحة والمنشئات إلا للتحويلات الصريحة (التهيئة المباشرة أو عملية الإرسال الصريحة) بينما يمكن استخدام المنشئات غير الصريحة ووظائف التحويل للتحويلات الضمنية والصريحة.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

مثال باستخدام الهياكل X, Y, Z والوظائف foo, bar, baz:

دعونا نلقي نظرة على مجموعة صغيرة من الهياكل والوظائف لمعرفة الفرق بينهما explicit وغيرexplicit التحويلات.

struct Z { };

struct X { 
  explicit X(int a); // X can be constructed from int explicitly
  explicit operator Z (); // X can be converted to Z explicitly
};

struct Y{
  Y(int a); // int can be implicitly converted to Y
  operator Z (); // Y can be implicitly converted to Z
};

void foo(X x) { }
void bar(Y y) { }
void baz(Z z) { }

أمثلة بخصوص المنشئ:

تحويل وسيطة دالة:

foo(2);                     // error: no implicit conversion int to X possible
foo(X(2));                  // OK: direct initialization: explicit conversion
foo(static_cast<X>(2));     // OK: explicit conversion

bar(2);                     // OK: implicit conversion via Y(int) 
bar(Y(2));                  // OK: direct initialization
bar(static_cast<Y>(2));     // OK: explicit conversion

تهيئة الكائن:

X x2 = 2;                   // error: no implicit conversion int to X possible
X x3(2);                    // OK: direct initialization
X x4 = X(2);                // OK: direct initialization
X x5 = static_cast<X>(2);   // OK: explicit conversion 

Y y2 = 2;                   // OK: implicit conversion via Y(int)
Y y3(2);                    // OK: direct initialization
Y y4 = Y(2);                // OK: direct initialization
Y y5 = static_cast<Y>(2);   // OK: explicit conversion

أمثلة بخصوص وظائف التحويل:

X x1{ 0 };
Y y1{ 0 };

تحويل وسيطة دالة:

baz(x1);                    // error: X not implicitly convertible to Z
baz(Z(x1));                 // OK: explicit initialization
baz(static_cast<Z>(x1));    // OK: explicit conversion

baz(y1);                    // OK: implicit conversion via Y::operator Z()
baz(Z(y1));                 // OK: direct initialization
baz(static_cast<Z>(y1));    // OK: explicit conversion

تهيئة الكائن:

Z z1 = x1;                  // error: X not implicitly convertible to Z
Z z2(x1);                   // OK: explicit initialization
Z z3 = Z(x1);               // OK: explicit initialization
Z z4 = static_cast<Z>(x1);  // OK: explicit conversion

Z z1 = y1;                  // OK: implicit conversion via Y::operator Z()
Z z2(y1);                   // OK: direct initialization
Z z3 = Z(y1);               // OK: direct initialization
Z z4 = static_cast<Z>(y1);  // OK: explicit conversion

لماذا الاستخدام explicit وظائف التحويل أو البنائين؟

قد تؤدي مُنشئات التحويل ووظائف التحويل غير الصريحة إلى حدوث غموض.

النظر في الهيكل V, ، للتحويل إلى int, ، هيكل U قابلة للإنشاء ضمنيًا من V ووظيفة f مثقلة ل U و bool على التوالى.

struct V {
  operator bool() const { return true; }
};

struct U { U(V) { } };

void f(U) { }
void f(bool) {  }

دعوة ل f يكون غامضًا في حالة تمرير كائن من النوع V.

V x;
f(x);  // error: call of overloaded 'f(V&)' is ambiguous

لا يعرف المترجم ما إذا كان سيستخدم المُنشئ أم لا U أو وظيفة التحويل لتحويل V كائن في نوع لتمريره إلى f.

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

قد تؤدي مُنشئات التحويل ووظائف التحويل غير الصريحة إلى سلوك غير متوقع.

فكر في وظيفة تطبع بعض المتجهات:

void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; }

إذا لم يكن مُنشئ حجم المتجه واضحًا، فمن الممكن استدعاء الدالة على النحو التالي:

print_intvector(3);

فماذا يتوقع المرء من مثل هذه الدعوة؟سطر واحد يحتوي على 3 أو ثلاثة أسطر تحتوي على 0؟(أين الثاني هو ما يحدث.)

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

كما يقول بيارن ستروستروب (في "لغة البرمجة C++"، الطبعة الرابعة، 35.2.1، ص.1011) في السؤال لماذا std::duration لا يمكن بناؤه ضمنيًا من رقم عادي:

إذا كنت تعرف ما تقصده، فكن واضحًا بشأنه.

مُنشئات التحويل الصريحة (C++ فقط)

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

class A
{
public:
    A();
    A(int);
    A(const char*, int = 0);
};

تعتبر التصريحات التالية قانونية:

A c = 1;
A d = "Venditti";

الإعلان الأول يعادل A c = A( 1 );.

إذا أعلنت منشئ الفصل كـ explicit, ، فإن التصريحات السابقة ستكون غير قانونية.

على سبيل المثال، إذا قمت بتعريف الفئة على النحو التالي:

class A
{
public:
    explicit A();
    explicit A(int);
    explicit A(const char*, int = 0);
};

يمكنك فقط تعيين القيم التي تطابق قيم نوع الفصل.

على سبيل المثال، تعتبر العبارات التالية قانونية:

  A a1;
  A a2 = A(1);
  A a3(1);
  A a4 = A("Venditti");
  A* p = new A(1);
  A a5 = (A)1;
  A a6 = static_cast<A>(1);

ال explicit-يمكن استخدام الكلمة الأساسية لفرض مُنشئ ليتم استدعاؤه صراحة.

class C{
public:
    explicit C(void) = default;
};

int main(void){
    C c();
    return 0;
}

ال explicit-الكلمة الرئيسية أمام المنشئ C(void) يخبر المترجم أنه يُسمح فقط بالاستدعاء الصريح لهذا المُنشئ.

ال explicit-يمكن أيضًا استخدام الكلمة الأساسية في مشغلي النوع المحدد من قبل المستخدم:

class C{
public:
    explicit inline operator bool(void) const{
        return true;
    }
};

int main(void){
    C c;
    bool b = static_cast<bool>(c);
    return 0;
}

هنا، explicit- الكلمة الأساسية تفرض فقط القوالب الصريحة لتكون صالحة، لذلك bool b = c; سيكون طاقم الممثلين غير صالح في هذه الحالة.في مثل هذه المواقف explicit-الكلمة الرئيسية يمكن أن تساعد المبرمج على تجنب عمليات التحويل الضمنية وغير المقصودة.وقد تم توحيد هذا الاستخدام في سي++11.

مرجع Cpp مفيد دائمًا !!!يمكن العثور على تفاصيل حول المحدد الصريح هنا.قد تحتاج إلى إلقاء نظرة على التحويلات الضمنية و تهيئة النسخ أيضاً.

نظرة سريعة

يحدد المحدد الصريح أن المُنشئ أو وظيفة التحويل (منذ C++ 11) لا تسمح بالتحويلات الضمنية أو تهيئة النسخ.

مثال على النحو التالي:

struct A
{
    A(int) { }      // converting constructor
    A(int, int) { } // converting constructor (C++11)
    operator bool() const { return true; }
};

struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};

int main()
{
    A a1 = 1;      // OK: copy-initialization selects A::A(int)
    A a2(2);       // OK: direct-initialization selects A::A(int)
    A a3 {4, 5};   // OK: direct-list-initialization selects A::A(int, int)
    A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
    A a5 = (A)1;   // OK: explicit cast performs static_cast
    if (a1) cout << "true" << endl; // OK: A::operator bool()
    bool na1 = a1; // OK: copy-initialization selects A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization

//  B b1 = 1;      // error: copy-initialization does not consider B::B(int)
    B b2(2);       // OK: direct-initialization selects B::B(int)
    B b3 {4, 5};   // OK: direct-list-initialization selects B::B(int, int)
//  B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
    B b5 = (B)1;   // OK: explicit cast performs static_cast
    if (b5) cout << "true" << endl; // OK: B::operator bool()
//  bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}

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

بالإضافة إلى ذلك، من الممارسات الجيدة دائمًا في البرمجة إنشاء مُنشئات وسيطة واحدة (بما في ذلك تلك التي لها قيم افتراضية لـ arg2,arg3,...) كما ذكرنا سابقًا.كما هو الحال دائمًا مع C++:إذا لم تفعل ذلك - فسوف تتمنى لو فعلت ذلك...

من الممارسات الجيدة الأخرى للفصول الدراسية جعل إنشاء النسخ والتخصيص أمرًا خاصًا (ويعرف أيضًا باسم.تعطيله) إلا إذا كنت بحاجة حقًا إلى تنفيذه.يؤدي هذا إلى تجنب وجود نسخ نهائية من المؤشرات عند استخدام الطرق التي سينشئها لك C++ افتراضيًا.هناك طريقة أخرى للقيام بذلك وهي مستمدة من Boost::noncopyable.

يقوم المنشئون بإلحاق التحويل الضمني.لمنع هذا التحويل الضمني، يلزم الإعلان عن مُنشئ باستخدام معلمة صريحة.

في C++ 11، يمكنك أيضًا تحديد "نوع عامل التشغيل ()" باستخدام هذه الكلمة الأساسية http://en.cppreference.com/w/cpp/language/explicit باستخدام هذه المواصفات، يمكنك استخدام عامل التشغيل فيما يتعلق بالتحويلات الصريحة والتهيئة المباشرة للكائن.

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

  • أعلى الرتب المتكاملة (char to int، float to double)؛
  • التحويلات القياسية (كثافة العمليات لمضاعفة)؛
  • تحويل مؤشرات الكائنات إلى فئة أساسية وإلى باطلة*؛
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top