Co jest złego w "sprawdzaniu do samodzielnego zadania" i co to znaczy?

W książce Herba Suttera wyjątkowa C++ (1999), ma słowa w rozwiązaniu punktu 10:

"Exception-unsafe "i" poor design " idą w parze. Jeśli fragment kodu nie jest bezpieczny dla wyjątków, jest to ogólnie w porządku i można go po prostu naprawić. Ale jeśli fragment kodu nie może być bezpieczny dla wyjątków ze względu na jego podstawową konstrukcję, prawie zawsze jest to sygnał jego słabego projektu.

Przykład 1: Funkcja Z dwoma różnymi obowiązkami jest trudna do wykonania wyjątek-Bezpieczny.

Przykład 2: operator przypisania kopii, który jest napisany w taki sposób, że musi sprawdzić, czy nie ma przypisania do siebie, prawdopodobnie nie jest silnie bezpieczny dla WYJĄTKÓW

Co on ma na myśli mówiąc "czek na samodzielne zadanie"?

[zapytanie]

Dave i AndreyT pokazują nam dokładnie, co oznacza "czek na samodzielne zadanie". To dobrze. Ale pytanie nie jest skończone. Dlaczego "sprawdzanie samodzielności" boli "bezpieczeństwo WYJĄTKÓW"(według Hurb Sutter)? Jeśli rozmówca spróbuje samodzielnie przyporządkować, to "sprawdzenie" działa tak, jakby nigdy nie doszło do przyporządkowania. Czy to naprawdę boli?

[notatka 1] w poz. 38 tożsamość obiektu później w książce Herba wyjaśnia on o przypisaniu sobie.

Author: Jimm Chen, 2012-08-18

4 answers

Ważniejsze pytanie w tym przypadku to, co oznacza "napisane w taki sposób, że musi sprawdzić, czy jest przypisane do siebie".

Oznacza to, że dobrze zaprojektowany operator przypisania nie powinien potrzebować, aby sprawdzić, czy nie ma przypisania. Przypisanie obiektu do siebie powinno działać poprawnie (tzn. mieć efekt końcowy "nic nie robiąc") bez wykonywania wyraźnego sprawdzenia Dla samo-przypisania.

Na przykład, gdybym chciał zaimplementować uproszczoną klasę array wzdłuż linii z

class array {
  ...
  int *data;
  size_t n;
};

I opracował następującą implementację operatora przyporządkowania

array &array::operator =(const array &rhs) 
{
  delete[] data;

  n = rhs.n;
  data = new int[n];
  std::copy_n(rhs.data, n, data);

  return *this;
}

To wdrożenie byłoby uważane za "złe", ponieważ oczywiście zawodzi w przypadku samodzielnego przypisania.

Aby to" naprawić " można albo dodać wyraźną SELF-assignment check

array &array::operator =(const array &rhs) 
{
  if (&rhs != this) 
  {
    delete[] data;

    n = rhs.n;
    data = new int[n];
    std::copy_n(rhs.data, n, data);
  }

  return *this;
}

Lub stosować podejście "bez kontroli"

array &array::operator =(const array &rhs) 
{
  size_t new_n = rhs.n;
  int *new_data = new int[new_n];
  std::copy_n(rhs.data, new_n, new_data);

  delete[] data;

  n = new_n;
  data = new_data;

  return *this;
}

To drugie podejście jest lepsze w tym sensie, że działa poprawnie w sytuacjach samodzielnego przypisania bez wyraźnego sprawdź. (Ta implementacja jest nadal daleka od doskonałości z punktu widzenia bezpieczeństwa wyjątków, jest tutaj, aby zilustrować różnicę między podejściem" sprawdzonym "i" bez sprawdzania " do obsługi samo-przydziału). Późniejsza implementacja check-less może być napisana bardziej elegancko dzięki dobrze znanemu idiomowi copy-and-swap.

Nie oznacza to, że należy unikać jednoznacznych kontroli do samodzielnego przypisania. Taka kontrola ma sens z punktu widzenia performance : nie ma punkt w przeprowadzaniu długiej sekwencji operacji tylko po to, aby w końcu "nic nie robić". Jednak w dobrze zaprojektowanym operatorze przypisania takie kontrole nie powinny być konieczne z punktu widzenia poprawności .

 31
Author: AnT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-19 23:11:11
MyClass& MyClass::operator=(const MyClass& other)  // copy assignment operator
{
    if(this != &other) // <-- self assignment check
    {
        // copy some stuff
    }

    return *this;
}

Przypisanie obiektu do siebie jest błędem, ale nie powinno to logicznie skutkować zmianą instancji klasy. Jeśli uda Ci się zaprojektować klasę, w której przypisanie do siebie ją zmienia, jest ona źle zaprojektowana.

 2
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-18 01:59:21

Z C++ core guide

Foo& Foo::operator=(const Foo& a)   // OK, but there is a cost
{
    if (this == &a) return *this;
    s = a.s;
    i = a.i;
    return *this;
}
Jest to oczywiście bezpieczne i wydajne. Jednakże, co jeśli zrobimy jedno samo zadanie na milion zadań? To około miliona zbędnych testów (ale ponieważ odpowiedź jest zasadniczo zawsze taka sama, predyktor gałęzi komputera będzie odgadywał właściwie za każdym razem). Consider:
Foo& Foo::operator=(const Foo& a)   // simpler, and probably much better
{
    s = a.s;
    i = a.i;
    return *this;
}

Uwaga: powyższy kod dotyczy tylko klas bez wskaźnika, dla klas z wskaźnikiem do pamięci dynamicznej. Prosimy o zapoznanie się z Odpowiedź mrówki.

 2
Author: camino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-09 21:11:19

Ogólny powód, aby sprawdzić samo-przypisanie jest ponieważ zniszczyć własne dane przed skopiowaniem w nowym. Ta struktura operatora przypisania nie jest również silnie bezpieczna dla WYJĄTKÓW.

Jako dodatek ustalono, że przypisanie do siebie nie przynosi korzyści w ogóle, ponieważ porównanie musi być wykonywane za każdym razem, ale przypisanie do siebie jest niezwykle rzadkie, a jeśli tak się dzieje, jest to błąd logiczny w twoim programie (naprawdę). Oznacza to, że w trakcie program, to tylko strata cykli.

 -1
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-18 03:04:33