Pytanie Co oznacza jawne słowo kluczowe?


Co robi explicit słowo kluczowe średnie w C ++?


2371
2017-09-23 13:58


pochodzenie


Chcę tylko wskazać każdemu, kto pojawił się w nowej wersji od czasu C ++ 11, explicit może być zastosowany do więcej niż tylko konstruktorów. Jest teraz poprawny, gdy zostanie zastosowany również do operatorów konwersji. Powiedz, że masz klasę BigInt z operatorem konwersji na int i wyraźny operator konwersji na std::string dla byle jakiego powodu. Będziesz mógł powiedzieć int i = myBigInt;, ale będziesz musiał rzucić bezpośrednio (używając static_castnajlepiej), żeby powiedzieć std::string s = myBigInt;. - chris
Nie można też jednoznacznie odwoływać się do zadania? (to znaczy. int x(5);) - Eitan Myron
theunixshell.blogspot.com/2013/01/explicit-keyword-in-c.html - Vijay
@chris Idea wyraźnej niejawnej konwersji jest absurdalna. Trzymaj się z dala od tego! - curiousguy
@curiousguy, nie ma czegoś takiego jak jawna niejawna konwersja. - chris


Odpowiedzi:


Kompilator może wykonać jedną niejawną konwersję, aby rozwiązać parametry funkcji. Oznacza to, że kompilator może używać konstruktorów wywoływanych przez a pojedynczy parametr konwertować z jednego typu na inny, aby uzyskać odpowiedni typ dla parametru.

Oto przykładowa klasa z konstruktorem, który może być użyty do niejawnych konwersji:

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

Oto prosta funkcja, która wymaga Foo obiekt:

void DoBar (Foo foo)
{
  int i = foo.GetFoo ();
}

i oto gdzie DoBar funkcja jest wywoływana.

int main ()
{
  DoBar (42);
}

Argument nie jest Foo obiekt, ale an int. Istnieje jednak konstruktor dla Foo to wymaga int więc ten konstruktor może być użyty do przekonwertowania parametru na poprawny typ.

Kompilator może to zrobić raz dla każdego parametru.

Prefixowanie explicit słowo kluczowe do konstruktora uniemożliwia kompilatorowi używanie tego konstruktora do niejawnych konwersji. Dodanie jej do powyższej klasy spowoduje błąd kompilatora podczas wywołania funkcji DoBar (42). Konieczne jest teraz wyraźne wywołanie konwersji za pomocą DoBar (Foo (42))

Powodem tego może być uniknięcie przypadkowej konstrukcji, która może ukryć błędy. Złożony przykład:

  • Masz MyString(int size) class z konstruktorem, który tworzy ciąg o danym rozmiarze. Masz funkcję print(const MyString&)i dzwonisz print(3) (kiedy ty tak właściwie zamierzał zadzwonić print("3")). Oczekujesz, że wydrukuje "3", ale zamiast tego wypisze pusty ciąg o długości 3.

2755
2017-09-23 14:09



ładne napisanie, możesz chcieć wspomnieć o wielu agentach z domyślnymi parametrami, które mogą działać jako pojedynczy argument, np. Object (const char * name = NULL, int otype = 0). - maccullt
Myślę, że należy również wspomnieć, że należy rozważyć jednoznaczne jawne konstruktory argumentów (mniej więcej automatycznie) i usunąć jawne słowo kluczowe tylko wtedy, gdy pożądana jest niejawna konwersja. przez projekt. Myślę, że contructors powinny być domyślnie jawne z "niejawnym" słowem kluczowym, aby umożliwić im pracę jako niejawne konwersje. Ale tak nie jest. - Michael Burr
@thecoshman: Nie deklarujesz parametr  explicit - deklarujesz a konstruktor  explicit. Ale tak: twoje parametry typu Foo muszą być skonstruowane explicitely, nie zostaną one zbudowane po cichu poprzez podłączenie parametrów konstruktora do funkcji. - Christian Severin
Tylko FYI, który wywołując "print (3)" w twoim przykładzie, funkcja musi być "print (const MyString &"). "Const" jest tutaj obowiązkowe, ponieważ 3 jest konwertowane na tymczasowy obiekt "MyString" i nie można powiązać tymczasowego z referencją, chyba że jest to "const" (jeszcze jedna na długiej liście gotcha C ++) - Larry
Dla kompletności dodam, że oprócz konwersji parametrów wyraźny tutaj słowo kluczowe uniemożliwi także użycie formularza przydziału dla ctor (np. Foo myFoo = 42;) i wymaga jawnych formularzy Foo myFoo = Foo (42); lub Foo myFoo (42); - Arbalest


Załóżmy, że masz klasę String:

class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

Teraz, jeśli spróbujesz:

String mystring = 'x';

Charakter 'x' zostanie niejawnie przekonwertowany na int a następnie String(int) zostanie wywołany konstruktor. Ale nie jest to zamierzone przez użytkownika. Aby więc zapobiec takim warunkom, określimy konstruktora jako explicit:

class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};

964
2017-09-23 16:37



Warto zauważyć, że powstają nowe uogólnione reguły inicjalizacji C ++ 0x String s = {0}; źle sformułowany, zamiast próbować wywołać inny konstruktor z pustym wskaźnikiem, jako String s = 0; zrobiłaby. - Johannes Schaub - litb
Mimo, że jest to stare pytanie, warto zwrócić uwagę na kilka rzeczy (lub na to, że ktoś mnie wyprostował). Tworząc formularz int lub obaj ctors, 'explicite', nadal będziesz miał ten sam błąd, jeśli używałeś String mystring('x') kiedy miałeś na myśli String mystring("x") nie zrobiłbyś tego? Ponadto, z powyższego komentarza widzę poprawione zachowanie String s = {0} koniec String s = 0 dzięki temu, że forma int jest "jawna". Ale, poza znaniem pierwszeństwa lekarzy, skąd znasz intencję (tj. Jak rozpoznać błąd) tego String s{0} ? - Arbalest
Dlaczego String mystring = 'x'; jest konwertowane na int? - InQusitive
@InQitiveitive: 'x'jest traktowany jako liczba całkowita, ponieważ char typ danych to tylko 1-bajtowa liczba całkowita. - DavidRR
Problem z twoim przykładem jest taki, że działa tylko z skopiuj inicjalizację (za pomocą =), ale nie z bezpośrednia inicjalizacja (bez używania =): kompilator nadal będzie wywoływał String(int) konstruktor bez generowania błędu, jeśli piszesz String mystring('x');, jak wskazał @Arbalest. The explicit słowo kluczowe służy do zapobiegania niejawnym konwersjom, które mają miejsce w bezpośredniej inicjalizacji i rozwiązywaniu funkcji. Lepszym rozwiązaniem dla twojego przykładu byłoby proste przeciążenie konstruktora: String(char c);. - Maggyero


W C ++ konstruktor z tylko jednym wymaganym parametrem jest uważany za niejawną funkcję konwersji. Konwertuje typ parametru na typ klasy. Czy to dobrze, czy nie zależy od semantyki konstruktora.

Na przykład, jeśli masz klasę ciągu z konstruktorem String(const char* s), to jest dokładnie to, czego chcesz. Możesz przekazać a const char* do funkcji oczekującej String, a kompilator automatycznie utworzy tymczasowy String obiekt dla ciebie.

Z drugiej strony, jeśli masz klasę bufora, której konstruktor Buffer(int size) przyjmuje rozmiar bufora w bajtach, prawdopodobnie nie chcesz, aby kompilator cicho skręcał ints do Buffers. Aby temu zapobiec, deklarujesz konstruktor za pomocą explicit słowo kluczowe:

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

W ten sposób,

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

staje się błędem podczas kompilacji. Jeśli chcesz zdać tymczasowy Buffer obiekt, musisz to zrobić jawnie:

useBuffer(Buffer(4));

Podsumowując, jeśli twój konstruktor z jednym parametrem konwertuje parametr na obiekt twojej klasy, prawdopodobnie nie chcesz go używać explicit słowo kluczowe. Ale jeśli masz konstruktor, który po prostu bierze jeden parametr, powinieneś zadeklarować go jako explicitaby zapobiec zaskakującemu kompilatorowi niespodziewanym konwersjom.


130
2017-10-08 14:43



useBufferoczekuje lwartości za swój argument, useBuffer(Buffer(4)) nie będzie działać z tego powodu. Zmieniając go, aby otrzymać const Buffer& lub Buffer&& Lub tylko Buffer sprawi, że to zadziała. - pqnet


Ta odpowiedź dotyczy tworzenia obiektów z / bez jawnego konstruktora, ponieważ nie jest uwzględniona w innych odpowiedziach.

Rozważmy następującą klasę bez jawnego konstruktora:

class Foo
{
public:
    Foo(int x) : m_x(x)
    {
    }

private:
    int m_x;
};

Obiekty klasy Foo można tworzyć na dwa sposoby:

Foo bar1(10);

Foo bar2 = 20;

W zależności od implementacji drugi sposób tworzenia klasy Foo może być mylący, lub nie to, co zamierzał programista. Prefixowanie explicit słowo kluczowe do konstruktora wygenerowałoby błąd kompilatora na Foo bar2 = 20;.

To jest zazwyczaj dobra praktyka do deklarowania konstruktów jednoargumentowych jako explicit, chyba że twoja implementacja wyraźnie zabrania tego.

Zauważ także, że konstruktorzy z

  • domyślne argumenty dla wszystkich parametrów, lub
  • domyślne argumenty dla drugiego parametru

mogą być używane jako konstruktory jednoargumentowe. Więc możesz chcieć je również explicit.

Przykład, kiedy celowo nie chcesz, aby twój konstruktor jednoargumentowy był jawny, jeśli tworzysz funktor (spójrz na strukturę "add_x" zadeklarowaną w to odpowiedź). W takim przypadku tworzenie obiektu jako add_x add30 = 30; prawdopodobnie miałoby sens.

Tutaj jest dobrym zapisem na jawnych konstruktorach.


34
2017-11-21 02:36





The explicit słowo kluczowe tworzy konstruktor konwersji na konstruktor nie będący konwersją. W rezultacie kod jest mniej podatny na błędy.


31
2017-07-10 23:48



prosta odpowiedź, jeśli znasz c ++, jeśli nie możesz przejść do odpowiedzi cjm ... - n611x007


Słowo kluczowe explicit towarzyszy obu

  • konstruktor klasy X, którego nie można użyć do niejawnej konwersji pierwszego (dowolnego) parametru na typ X

C ++ [klasa.conv.ctor]

1) Konstruktor zadeklarowany bez jawnego specyfikatora funkcji określa konwersję z typów jej parametrów na typ jego klasy. Taki konstruktor jest nazywany konstruktem konwertującym.

2) Konstruktor jawny konstruuje obiekty tak samo, jak konstruktory nie jawne, ale robi to tylko wtedy, gdy bezpośrednie użycie składni inicjalizacji (8.5) lub rzutowania (5.2.9, 5.4) są jawnie użyte. Domyślny konstruktor może być jawnym konstruktorem; taki konstruktor zostanie użyty do wykonania domyślnej inicjalizacji lub wartościowania   (8.5).

  • lub funkcja konwersji, która jest brana pod uwagę tylko do bezpośredniej inicjalizacji i jawnej konwersji.

C ++ [class.conv.fct]

2) Funkcja konwersji może być jawna (7.1.2), w którym to przypadku traktowana jest tylko jako zdefiniowana przez użytkownika konwersja dla bezpośredniej inicjalizacji (8.5). W przeciwnym razie konwersje zdefiniowane przez użytkownika nie są ograniczone do użycia w zadaniach   i inicjalizacje.

Przegląd

Jawne funkcje konwersji i konstruktory mogą być używane tylko do jawnych konwersji (bezpośrednia inicjalizacja lub jawna operacja rzutowania), podczas gdy nie jawne konstruktory i funkcje konwersji mogą być używane do niejawnych i jawnych konwersji.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Przykład użycia struktur X, Y, Z i funkcje foo, bar, baz:

Spójrzmy na małą konfigurację struktur i funkcji, aby zobaczyć różnicę między expliciti nieexplicit konwersje.

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) { }

Przykłady dotyczące konstruktora:

Konwersja argumentu funkcji:

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

Inicjalizacja obiektu:

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

Przykłady dotyczące funkcji konwersji:

X x1{ 0 };
Y y1{ 0 };

Konwersja argumentu funkcji:

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

Inicjalizacja obiektu:

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

Dlaczego warto korzystać explicit funkcje konwersji lub konstruktory?

Konstruktory konwersji i nie-jawne funkcje konwersji mogą wprowadzać niejednoznaczność.

Zastanów się nad strukturą V, zamienny na int, struktura U niejawnie możliwe do skonstruowania z V i funkcja f przeciążony dla U i bool odpowiednio.

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

struct U { U(V) { } };

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

Połączenie z f jest niejednoznaczne, jeśli przekazuje obiekt typu V.

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

Kompilator nie wie, czy użyć konstruktora U lub funkcja konwersji do konwersji V obiekt w typ do przekazania f.

Jeśli którykolwiek z konstruktorów U lub funkcja konwersji V byłoby explicitnie byłoby dwuznaczności, ponieważ brano pod uwagę tylko konwersję nie tylko jawną. Jeśli oba są jawne, połączenie z f używanie obiektu typu V musiałoby zostać wykonane przy użyciu jawnej operacji konwersji lub przesyłania.

Konstruktory konwersji i nie jawne funkcje konwersji mogą prowadzić do nieoczekiwanego zachowania.

Zastanów się nad funkcją drukowania jakiegoś wektora:

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

Gdyby konstruktor wielkości wektora nie był jawny, byłoby możliwe wywołanie funkcji w następujący sposób:

print_intvector(3);

Czego można oczekiwać od takiego połączenia? Jedna linia zawierająca 3 lub trzy linie zawierające 0? (Gdzie jest drugie, co się dzieje.)

Korzystanie z jawnego słowa kluczowego w interfejsie klasy wymusza na użytkowniku interfejsu jawne informacje o pożądanej konwersji.

Jak to ujął Bjarne Stroustrup (w "The C ++ Programming Language", 4th Ed., 35.2.1, pp. 1011) na pytanie dlaczego std::duration nie może być domyślnie skonstruowany z liczby zwykłej:

Jeśli wiesz, co masz na myśli, powiedz o tym jasno.


31
2018-05-14 09:28





The explicit-słowo może być użyte do wymuszenia wywołania konstruktora wyraźnie.

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

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

explicit-keyword przed konstruktorem C(void) mówi kompilatorowi, że dozwolone jest jawne wywołanie tego konstruktora.

The explicit-słowo może być również używane w operacjach rzutowania typu zdefiniowanych przez użytkownika:

class C{
public:
    explicit inline operator bool(void) const{
        return true;
    }
};

int main(void){
    C c;
    bool b = static_cast<bool>(c);
    return 0;
}

Tutaj, explicit-keyword wymusza prawidłowe tylko rzutowanie, więc bool b = c; byłaby w tym przypadku nieważna. W takich sytuacjach explicit-słowo może pomóc programistom uniknąć niejawnych, niezamierzonych rzutów. To użycie zostało znormalizowane w C ++ 11.


25
2017-10-01 22:00



C c(); w pierwszym przykładzie nie oznacza to, co myślisz, że oznacza: jest to deklaracja funkcji o nazwie c który nie przyjmuje parametrów i zwraca instancję C. - 6502
explicit operator bool() jest także wersją bezpieczną bool C ++ 11 i może być używana pośrednio w sprawdzaniu stanu (i tylko w zakresie kontroli stanu, o ile wiem. W twoim drugim przykładzie ta linia również byłaby ważna w main(): if (c) { std::cout << "'c' is valid." << std:: endl; }. Poza tym nie można go używać bez jawnego rzucania. - Justin Time
"konstruktor, który będzie wywoływany jawnie"nie - curiousguy
@ JustinTime Jest to niedziałająca, zepsuta wersja bezpiecznego boola. Cała idea jawnej niejawnej konwersji jest absurdalna. - curiousguy
@curiousguy True. Wygląda to trochę jak kludge, którego celem jest raczej łatwe zapamiętywanie (prawdopodobnie w nadziei na to, że tłumaczenie jest często używane), niż przy podążaniu za angielską logiką, i zaprojektowane tak, aby nie były niekompatybilne z wcześniejszymi bezpiecznymi implementacjami bool (więc jesteś mniejszy prawdopodobnie coś złamie, jeśli ją zamienisz). Przynajmniej IMO. - Justin Time