[C++] 顯式關鍵字是什麼意思?


Answers

假設你有一個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
};
Question

C ++中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 ,一個可以從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'; }

如果向量的大小構造函數不是顯式的,則可以像這樣調用函數:

print_intvector(3);

這樣的電話會給人甚麼期望? 一行包含3或3行包含0 ? (第二個是發生了什麼。)

在類接口中使用顯式關鍵字會強制接口的用戶對所需的轉換進行明確的說明。

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

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




這個答案是關於使用/不使用顯式構造函數的對象創建,因為它在其他答案中沒有涉及。

考慮下面的類沒有顯式的構造函數:

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;處生成編譯器錯誤Foo bar2 = 20;

除非您的實現明確禁止,否則通常將單參數構造函數聲明為explicit

還要注意帶有的構造函數

  • 所有參數的默認參數,或
  • 第二個參數的默認參數

都可以用作單參數構造函數。 所以你可能想要explicit這些。

當你故意不想讓你的單參數構造函數顯式化的時候,例子是你創建了一個函子(看看this答案中聲明的'add_x'結構體)。 在這種情況下,創建一個對象為add_x add30 = 30; 可能會有意義。

Here是一個很好的關於顯式構造函數的文章。




這已經被討論過了( 什麼是顯式構造函數 )。 但我必須說,它缺乏這裡找到的詳細描述。

此外,如前所述,構建一個參數構造函數(包括arg2,arg3,...的默認值)將始終是一種良好的編碼習慣。 像C ++一樣:如果你不這樣做 - 你會希望你做到了......

除非你真的需要實現它,否則另一個良好的類實踐是將拷貝構造和賦值私有化(也稱為禁用它)。 這樣可以避免在使用C ++為您默認創建的方法時最終獲得指針副本。 另一種做法是從boost :: noncopyable派生。




構造函數附加隱式轉換。 為了抑制這種隱式轉換,需要聲明一個帶有明確參數的構造函數。

在C ++ 11中,您還可以使用關鍵字here來指定“運算符類型()”使用此類規範,您可以使用運算符進行顯式轉換,並且直接初始化對象。

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

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





Links