explicit
C++ でのキーワードの意味は何ですか?
10 に答える
コンパイラは、パラメーターを関数に解決するために 1 つの暗黙的な変換を行うことができます。これが意味することは、コンパイラは、単一のパラメーターで呼び出し可能なコンストラクターを使用して、ある型から別の型に変換して、パラメーターの適切な型を取得できるということです。
暗黙的な変換に使用できるコンストラクターを持つクラスの例を次に示します。
class Foo
{
private:
int m_foo;
public:
// single parameter constructor, can be used as an implicit conversion
Foo (int foo) : m_foo (foo) {}
int GetFoo () { return m_foo; }
};
Foo
オブジェクトを受け取る単純な関数を次に示します。
void DoBar (Foo foo)
{
int i = foo.GetFoo ();
}
DoBar
関数が呼び出される場所は次のとおりです。
int main ()
{
DoBar (42);
}
引数はFoo
オブジェクトではなく、int
. ただし、 を受け取るコンストラクターが存在するFoo
ためint
、このコンストラクターを使用してパラメーターを正しい型に変換できます。
コンパイラーは、パラメーターごとにこれを 1 回行うことができます。
コンストラクターの前にキーワードを付けるとexplicit
、コンパイラーはそのコンストラクターを暗黙的な変換に使用できなくなります。上記のクラスに追加すると、関数呼び出しでコンパイラ エラーが発生しますDoBar (42)
。明示的に変換を呼び出す必要があります DoBar (Foo (42))
これを行う理由は、バグを隠す可能性のある偶発的な構築を避けるためです。
考案された例:
MyString
指定されたサイズの文字列を構築するコンストラクタを持つクラスがあります。関数print(const MyString&)
(およびオーバーロードprint (char *string)
)があり、 (実際に呼び出すつもりだったprint(3)
ときに)呼び出します。「3」を出力することを期待していますが、代わりに長さ 3 の空の文字列を出力します。print("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++ では、必須パラメーターが 1 つだけのコンストラクターは、暗黙的な変換関数と見なされます。パラメータの型をクラスの型に変換します。これが良いことかどうかは、コンストラクターのセマンティクスに依存します。
たとえば、コンストラクターを持つ文字列クラスがある場合、それはString(const char* s)
おそらくまさにあなたが望むものです。const char*
を期待する関数に a を渡すことができString
、コンパイラは自動的に一時String
オブジェクトを構築します。
一方、コンストラクターがバッファーのサイズをバイト単位で受け取るバッファー クラスがある場合は、コンパイラーが静かにs を s に変換Buffer(int size)
することを望まないでしょう。これを防ぐには、次のキーワードを使用してコンストラクターを宣言します。int
Buffer
explicit
class Buffer { explicit Buffer(int size); ... }
そうすれば、
void useBuffer(Buffer& buf);
useBuffer(4);
コンパイル時エラーになります。一時オブジェクトを渡したい場合Buffer
は、明示的に行う必要があります。
useBuffer(Buffer(4));
explicit
要約すると、単一パラメーターのコンストラクターがパラメーターをクラスのオブジェクトに変換する場合、おそらくキーワードを使用したくないでしょう。ただし、たまたま単一のパラメーターを受け取るコンストラクターがある場合はexplicit
、コンパイラーが予期しない変換で驚かないように、コンストラクターを宣言する必要があります。
キーワードexplicit
には、
- 最初の (任意の唯一の) パラメータを型 X に暗黙的に変換するために使用できないクラス X のコンストラクタ
C++ [class.conv.ctor]
1) function-specifier explicit なしで宣言されたコンストラクターは、そのパラメーターの型からそのクラスの型への変換を指定します。このようなコンストラクターは、変換コンストラクターと呼ばれます。
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);
そのような電話から何を期待するでしょうか??を含む 1 行3
または 3 行 0
(2 つ目は何が起こるかです。)
クラス インターフェイスで明示的なキーワードを使用すると、インターフェイスのユーザーは、目的の変換について明示的になる必要があります。
Bjarne Stroustrup が (「The C++ Programming Language」、第 4 版、35.2.1、pp. 1011 で) なぜstd::duration
単純な数値から暗黙的に構築できないのかという質問について次のように述べています。
言いたいことがわかっている場合は、それについて明確に説明してください。
この回答は、他の回答ではカバーされていないため、明示的なコンストラクターを使用する/使用しないオブジェクトの作成に関するものです。
明示的なコンストラクターのない次のクラスを検討してください。
class Foo
{
public:
Foo(int x) : m_x(x)
{
}
private:
int m_x;
};
クラス Foo のオブジェクトは、次の 2 つの方法で作成できます。
Foo bar1(10);
Foo bar2 = 20;
実装によっては、クラス Foo をインスタンス化する 2 番目の方法がわかりにくい場合や、プログラマーの意図とは異なる場合があります。コンストラクターの前にキーワードを付けると、explicit
でコンパイラ エラーが発生しFoo bar2 = 20;
ます。
実装で明確に禁止されていない限り、単一引数のコンストラクターを として宣言することは、通常は良い習慣です。explicit
また、コンストラクターは
- すべてのパラメータのデフォルト引数、または
- 2 番目以降のパラメータのデフォルト引数
どちらも単一引数のコンストラクターとして使用できます。したがって、これらも作成することをお勧めしますexplicit
。
単一引数コンストラクターを明示的にしたくない場合の例は、ファンクターを作成している場合です (この回答で宣言されている 'add_x' 構造体を見てください)。そのような場合、オブジェクトを作成することadd_x add30 = 30;
はおそらく理にかなっています。
ここには、明示的なコンストラクターに関する優れた記事があります。
キーワードはexplicit
、変換コンストラクターを非変換コンストラクターにします。その結果、コードはエラーが発生しにくくなります。
-キーワードを使用して、コンストラクターを明示的explicit
に呼び出すように強制できます。
class C {
public:
explicit C() =default;
};
int main() {
C c;
return 0;
}
explicit
コンストラクターの前にある- キーワードはC()
、このコンストラクターへの明示的な呼び出しのみが許可されることをコンパイラーに伝えます。
-explicit
キーワードは、ユーザー定義の型キャスト演算子でも使用できます。
class C{
public:
explicit inline operator bool() const {
return true;
}
};
int main() {
C c;
bool b = static_cast<bool>(c);
return 0;
}
ここで、explicit
-keyword は明示的なキャストのみを有効にするよう強制するためbool b = c;
、この場合は無効なキャストになります。このような状況では、explicit
-keyword は、プログラマーが暗黙的で意図しないキャストを回避するのに役立ちます。この使用法はC++11で標準化されています。