c++ cppreference 顯式關鍵字是什麼意思?




c++ constructor cppreference (9)

explicit關鍵字在C ++中的含義是什麼?


顯式轉換構造函數(僅限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);

假設你有一個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 ++中,只有一個必需參數的構造函數被認為是隱式轉換函數。 它將參數類型轉換為類類型。 這是否是好事取決於構造函數的語義。

例如,如果你有一個帶有構造函數String(const char* s)的字符串類,那可能就是你想要的。 您可以將const char*傳遞給期望String的函數,編譯器將自動為您構造臨時String對象。

另一方面,如果你有一個緩衝類,其構造函數Buffer(int size)佔用緩衝區的大小(以字節為單位),你可能不希望編譯器悄悄地將int轉換為Buffer s。 要防止這種情況,請使用explicit關鍵字聲明構造函數:

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

那樣,

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

成為編譯時錯誤。 如果要傳遞臨時Buffer對象,則必須明確地執行此操作:

useBuffer(Buffer(4));

總之,如果您的單參數構造函數將參數轉換為類的對象,您可能不希望使用explicit關鍵字。 但是如果你有一個只需要一個參數的構造函數,那麼你應該將它聲明為explicit以防止編譯器因意外的轉換而讓你感到驚訝。


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
}

explicit -keyword可用於強制顯式調用構造函數

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

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

構造函數C(void)前面的explicit -keyword告訴編譯器只允許顯式調用此構造函數。

explicit -keyword也可以在用戶定義的類型轉換操作符中使用:

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 -keyword只強制顯式轉換有效,所以bool b = c; 在這種情況下,將是無效的演員。 在這些情況下, explicit -keyword可以幫助程序員避免隱式的,非預期的強制轉換。 這種用法已在C++11標準化。


允許編譯器進行一次隱式轉換以將參數解析為函數。 這意味著編譯器可以使用可用單個參數調用的構造函數從一種類型轉換為另一種類型,以便為參數獲取正確的類型。

這是一個帶有構造函數的示例類,可用於隱式轉換:

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))明確調用轉換DoBar (Foo (42))

您可能希望這樣做的原因是為了避免可以隱藏錯誤的意外構造。 舉例:

  • 你有一個MyString(int size)類,它有一個構造函數,用於構造給定大小的字符串。 你有一個函數print(const MyString&) ,你調用print(3) (當你打算調用print("3") )。 你希望它打印“3”,但它打印一個長度為3的空字符串。

構造函數附加隱式轉換。 要禁止此隱式轉換,需要聲明帶有顯式參數的構造函數。

在C ++ 11中,您還可以使用關鍵字here指定“operator type()”。使用此類規範,您可以在顯式轉換方面使用運算符,以及直接初始化對象。

PS當使用USER定義的轉換(通過構造函數和類型轉換運算符)時,只允許使用一級隱式轉換。 但您可以將此轉化與其他語言轉換結合使用

  • 積分排名(char為int,float為double);
  • 標準轉換(int to double);
  • 將對象的指針轉換為基類並將其轉換為void *;

關鍵字explicit伴隨著

  • 類X的構造函數,不能用於將第一個(任何唯一的)參數隱式轉換為類型X.

C ++ [class.conv.ctor]

1)在沒有函數說明符explicit的情況下聲明的構造函數指定從其參數類型到其類類型的轉換。 這樣的構造函數稱為轉換構造函數。

2)顯式構造函數與非顯式構造函數一樣構造對象,但僅在顯式使用直接初始化語法(8.5)或強制轉換(5.2.9,5.4)的情況下才這樣做。 默認構造函數可以是顯式構造函數; 這樣的構造函數將用於執行默認初始化或valueinitialization(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 ,一個可從V隱式構造的結構U和一個分別為Ubool重載的函數f

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

struct U { U(V) { } };

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

如果傳遞V類型的對象,則對f的調用是不明確的。

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

編譯器不知道使用U的構造函數或轉換函數將V對象轉換為傳遞給f的類型。

如果U的構造函數或V的轉換函數是explicit ,則不會有歧義,因為只考慮非顯式轉換。 如果兩者都是顯式的,則必須使用顯式轉換或強制轉換操作來使用類型V的對象調用f

轉換構造函數和非顯式轉換函數可能會導致意外行為。

考慮打印一些向量的函數:

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

如果向量的size-constructor不是顯式的,則可以像這樣調用函數:

print_intvector(3);

人們對這樣的電話會有什麼期望? 一行包含3行或3行包含0 ? (第二個是發生的事情。)

在類接口中使用explicit關鍵字可強制接口的用戶明確指出所需的轉換。

正如Bjarne Stroustrup所說的那樣(在“The C ++ Programming Language”,第4版,35.2.1,第1011頁)中,為什麼std::duration不能用普通數字隱式構造:

如果你知道你的意思,請明確說明。





explicit-constructor