c++ - ماذا تعني الكلمة الرئيسية الصريحة؟




constructor explicit (8)

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


الكلمة الأساسية 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 overloaded لـ 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

لا يعرف المحول البرمجي wether لاستخدام منشئ 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 ؟ (حيث يكون الثاني هو ما يحدث.)

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

كما يضعها Bjarne Stroustrup (في "لغة برمجة C ++" ، الطبعة الرابعة ، 35.2.1 ، ص. 1011) على السؤال لماذا لا يمكن إنشاء std::duration بشكل ضمني من رقم عادي:

إذا كنت تعرف ما تعنيه ، فكن صريحًا بشأنه.


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

نظرة سريعة

يحدد المُعرّف الصريح أن المُنشئ أو وظيفة التحويل (حيث أن 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
}

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

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

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

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

بهذه الطريقة،

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

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

useBuffer(Buffer(4));

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


لنفترض أن لديك 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
};

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

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

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


يضيف المؤلفون التحويل الضمني. لقمع هذا التحويل الضمني ، يُطلب منه أن يعلن عن مُنشئ ذو معلمة صريحة.

في C ++ 11 ، يمكنك أيضًا تحديد "نوع مشغل ()" باستخدام هذه الكلمة الرئيسية here باستخدام هذه المواصفات ، يمكنك استخدام المشغل من حيث التحويلات الصريحة ، و التهيئة المباشرة للكائن.

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

  • حتى الرتب المتكاملة (char to int ، تعويم لمضاعفة) ؛
  • التحويلات ستاندارت (كثافة العمليات لمضاعفة) ؛
  • تحويل مؤشرات الكائنات إلى الفئة الأساسية وإبطال *؛

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

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

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 (42) . من الضروري الآن المطالبة بالتحويل بشكل صريح باستخدام DoBar (Foo (42))

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

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

منشئي التحويل الصريح (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-constructor