web-dev-qa-db-ja.com

Explicitキーワードはどういう意味ですか?

C++でexplicitキーワードはどういう意味ですか?

2605
Skizz

コンパイラーは、パラメーターを関数に解決するために暗黙的な変換を1回行うことができます。これが意味することは、コンパイラは 単一のパラメータ とともに呼び出し可能なコンストラクタを使用して、パラメータの正しい型を取得するためにある型から別の型に変換できることです。

これは暗黙的な変換に使用できるコンストラクタを持つクラスの例です。

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のコンストラクタが存在するため、このコンストラクタを使用してパラメータを正しい型に変換できます。

コンパイラーは各パラメーターにつき1回これを行うことができます。

コンストラクタの先頭にexplicitキーワードを付けると、コンパイラは暗黙の変換にそのコンストラクタを使用できなくなります。上記のクラスに追加すると、関数呼び出しDoBar (42)でコンパイラエラーが発生します。 DoBar (Foo (42))で明示的に変換を呼び出す必要があります

あなたがこれをしたいと思うかもしれない理由はバグを隠すことができる偶然の建設を避けるためです。考案された例:

  • 与えられたサイズの文字列を構築するコンストラクタを持つMyString(int size)クラスがあります。あなたは関数print(const MyString&)を持っています、そしてあなたはprint(3)を呼び出します(あなたが 実際に print("3")を呼び出すことを意図していた時)。あなたはそれが "3"を印刷することを期待していますが、代わりに長さ3の空の文字列を印刷します。
3001
Skizz

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
};
1039
Eddie

C++では、必須パラメーターが1つだけのコンストラクターは、暗黙的な変換関数と見なされます。パラメータの型をクラスの型に変換します。これが良いことであるかどうかは、コンストラクタのセマンティクスに依存します。

たとえば、コンストラクタString(const char* s)を持つ文字列クラスがある場合、それはおそらくあなたが望むものです。 Stringを期待する関数にconst char*を渡すことができます、そしてコンパイラはあなたのために一時的なStringオブジェクトを自動的に構築します。

一方、コンストラクタBuffer(int size)がバッファのサイズをバイト単位で取るバッファクラスがある場合、おそらくコンパイラにintsをBuffersに静かに変換させたくないでしょう。それを防ぐために、コンストラクタをexplicitキーワードで宣言します。

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

そのように、

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

コンパイル時エラーになります。一時的なBufferオブジェクトを渡したい場合は、明示的に渡す必要があります。

useBuffer(Buffer(4));

まとめると、単一パラメータのコンストラクタがパラメータをクラスのオブジェクトに変換する場合、おそらくexplicitキーワードを使用したくないでしょう。しかし、たった1つのパラメータをとるようなコンストラクタがある場合は、コンパイラが予期しない変換をしても驚かないように、それをexplicitとして宣言する必要があります。

145
cjm

この答えは他の答えでカバーされていないので明白なコンストラクタの有無にかかわらずオブジェクト作成についてである。

明示的なコンストラクタのない次のクラスを考えてください。

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にしたいと思うかもしれません。

意図的にnotにして単一引数のコンストラクタを明示的にしたい場合の例は、ファンクタを作成している場合です( this で宣言された 'add_x'構造体を見てくださいそのような場合、add_x add30 = 30;としてオブジェクトを作成することはおそらく意味があります。

Here は明示的なコンストラクタについての良い記事です。

39
Gautam

explicitキーワードは、変換コンストラクタを非変換コンストラクタにします。その結果、コードのエラーが少なくなります。

37
SankararaoMajji

キーワードexplicitはどちらかを伴います

  • 最初の(任意の唯一の)パラメータを型Xに暗黙的に変換するために使用できないクラスXのコンストラクタ /

C++ [class.conv.ctor]

1)関数指定子を明示的に指定せずに宣言されたコンストラクタは、そのパラメータの型からそのクラスの型への変換を指定します。このようなコンストラクタは変換コンストラクタと呼ばれます。

2)明示的コンストラクタは、非明示的コンストラクタとまったく同じようにオブジェクトを構成しますが、直接初期化構文(8.5)またはキャスト(5.2.9、5.4)が明示的に使用されている場合にのみそうします。デフォルトのコンストラクタは明示的なコンストラクタにすることができます。そのようなコンストラクタは、default-initializationまたは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変換とnon -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に変換可能な構造体intUから暗黙的に構築可能な構造体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を含む1行または0を含む3行? (2番目のものは何が起こるかです。)

クラスインターフェイスでexplicitキーワードを使用すると、インターフェイスのユーザーは目的の変換について明示的になります。

Bjarne Stroustrupはそれを( "The C++ Programming Language"、4th Ed。、35.2.1、pp。1011)、なぜstd::durationが普通の数から暗黙のうちに構築することができないのかという疑問に置きます。

自分の意味を知っている場合は、それについて明確にしてください。

36
Pixelchemist

explicit-キーワードは 明示的に と呼ばれるようにコンストラクタを強制するために使用することができます。

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

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

コンストラクタC(void)の前にあるexplicit-キーワードは、このコンストラクタへの明示的な呼び出しだけが許可されることをコンパイラに伝えます。

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- keywordは明示的なキャストのみが有効であることを強制しているため、この場合はbool b = c;は無効なキャストになります。これらのexplicit-キーワードのような状況では、暗黙の意図しないキャストを避けるためにプログラマーを助けることができます。この用法は C++ 11 で標準化されています。

26
Helixirr

明示的変換コンストラクタ(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);
23
coding_ninza

これはすでに議論されています( 明示的コンストラクタとは )。しかし、私はそれがここに見られる詳細な説明を欠いていると言わなければなりません。

その上、すでに述べたように、引数を1つのコンストラクタ(arg2、arg3、...のデフォルト値を持つものも含む)にすることは、常に良いコーディング方法です。 C++といつものように:そうでなければ - あなたはそうしたかったでしょう….

あなたが本当にそれを実装する必要がない限り、クラスのためのもう一つの良い習慣はコピー構築と代入を非公開にすることです(別名それを無効にする)。これにより、C++がデフォルトで作成するメソッドを使用するときに、最終的にポインタのコピーが作成されるのを防ぎます。これを行う他の方法はboost :: noncopyableから派生することである。

18
fmuecke

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
}
18
selfboot

コンストラクタは暗黙的な変換を追加します。この暗黙の型変換を抑制するためには、パラメータexplicitを使ってコンストラクタを宣言する必要があります。

C++ 11では、そのようなキーワードで "operator type()"を指定することもできます http://en.cppreference.com/w/cpp/language/explicit このような仕様では、次のような演算子を使用できます。明示的な変換、およびオブジェクトの直接初期化.

P.S BYコンストラクターおよび型変換演算子によってBY USERで定義された変換を使用する場合は、暗黙的な変換を1レベルだけ使用できます。しかし、あなたは他の言語変換とこの変換を組み合わせることができます

  • 整数ランクまで(charからint、floatからdouble)
  • 標準変換(intからdouble)。
  • オブジェクトのポインタを基本クラスとvoid *に変換します。
5
bruziuz