Share via


Explicitně přednastavené a odstraněné funkce

V jazyce C++11 poskytují výchozí a odstraněné funkce explicitní kontrolu nad tím, jestli se speciální členské funkce automaticky generují. Odstraněné funkce také poskytují jednoduchý jazyk, abyste zabránili problematickému povýšení typů v argumentech u funkcí všech typů – speciálních členských funkcí a normálních členských funkcí a nečlenných funkcí– což by jinak způsobilo nežádoucí volání funkce.

Výhody explicitně výchozích a odstraněných funkcí

V jazyce C++ kompilátor automaticky vygeneruje výchozí konstruktor, konstruktor copy, operátor copy-assignment a destruktor pro typ, pokud deklaruje vlastní. Tyto funkce se označují jako speciální členské funkce a to, co dělá jednoduché uživatelsky definované typy v jazyce C++ se chovají jako struktury v jazyce C. To znamená, že můžete vytvářet, kopírovat a zničit je bez dalšího úsilí o kódování. C++11 přináší sémantiku přesunutí do jazyka a přidá konstruktor přesunutí a operátor přiřazení k seznamu speciálních členských funkcí, které může kompilátor automaticky generovat.

To je vhodné pro jednoduché typy, ale složité typy často definují jednu nebo více speciálních členských funkcí samotných, což může zabránit automatickému generování dalších speciálních členských funkcí. V praxi:

  • Pokud je jakýkoli konstruktor explicitně deklarován, nevygeneruje se automaticky žádný výchozí konstruktor.

  • Pokud je virtuální destruktor explicitně deklarován, nevygeneruje se automaticky žádný výchozí destruktor.

  • Pokud je konstruktor přesunutí nebo operátor přiřazení přesunutí explicitně deklarován, pak:

    • Negeneruje se automaticky žádný konstruktor kopírování.

    • Automaticky se nevygeneruje žádný operátor přiřazení kopírování.

  • Pokud je konstruktor kopírování, operátor copy-assignment, konstruktor move, operátor move-assignment nebo destruktor explicitně deklarován, pak:

    • Negeneruje se automaticky žádný konstruktor přesunutí.

    • Není automaticky generován žádný operátor přiřazení přesunutí.

Poznámka:

Kromě toho standard C++11 určuje následující další pravidla:

  • Pokud je konstruktor nebo destruktor kopírování explicitně deklarován, je automatické generování operátoru přiřazení kopírování zastaralé.
  • Pokud je operátor přiřazení kopírování nebo destruktor explicitně deklarován, je automatické generování konstruktoru kopírování zastaralé.

V obou případech visual Studio automaticky generuje nezbytné funkce implicitně a ve výchozím nastavení nevygeneruje upozornění. Vzhledem k tomu, že Visual Studio 2022 verze 17.7, je možné povolit C5267 vygenerovat upozornění.

Důsledky těchto pravidel mohou také uniknout do hierarchií objektů. Pokud například z jakéhokoli důvodu základní třída nemá výchozí konstruktor, který je volán z odvozené třídy , tj. konstruktor protected nebo konstruktor, public který nepřijímá žádné parametry, pak třída odvozená z ní nemůže automaticky vygenerovat vlastní výchozí konstruktor.

Tato pravidla můžou komplikovat implementaci toho, co by mělo být jednoduché, uživatelem definované typy a běžné idiomy jazyka C++, například znepřístupnění uživatelem definovaného typu tím, že deklaruje konstruktor kopírování a operátor přiřazení kopírování soukromě a nedefinuje je.

struct noncopyable
{
  noncopyable() {};

private:
  noncopyable(const noncopyable&);
  noncopyable& operator=(const noncopyable&);
};

Před C++11 byl tento fragment kódu idiomatickou formou necopyable typů. Má ale několik problémů:

  • Konstruktor kopírování musí být deklarován soukromě, aby ho skryl, ale protože je deklarován vůbec, automatické generování výchozího konstruktoru je zabráněno. Pokud chcete, musíte explicitně definovat výchozí konstruktor, a to i v případě, že nic nedělá.

  • I když explicitně definovaný výchozí konstruktor nic nedělá, kompilátor ho považuje za netriviální. Je méně efektivní než automaticky vygenerovaný výchozí konstruktor a zabraňuje noncopyable tomu, aby byl skutečný typ POD.

  • I když je konstruktor kopírování a operátor přiřazení kopírování skrytý před vnějším kódem, členské funkce a přátelé noncopyable můžou stále vidět a volat je. Pokud jsou deklarovány, ale nejsou definované, způsobí jejich volání chybu linkeru.

  • I když se jedná o běžně přijímaný idiom, záměr není jasný, pokud nerozumíte všem pravidlu pro automatické generování speciálních členských funkcí.

V jazyce C++11 lze necopyable idiom implementovat způsobem, který je jednodušší.

struct noncopyable
{
  noncopyable() =default;
  noncopyable(const noncopyable&) =delete;
  noncopyable& operator=(const noncopyable&) =delete;
};

Všimněte si, jak se řeší problémy s idiomem pre-C++11:

  • Generování výchozíhokonstruktho

  • Explicitní výchozí speciální členské funkce jsou stále považovány za triviální, takže neexistuje žádné snížení výkonu a noncopyable nebrání tomu, aby byl skutečný typ POD.

  • Konstruktor kopírování a operátor přiřazení kopírování jsou veřejné, ale odstraněny. Jedná se o chybu v době kompilace, která definuje nebo volá odstraněnou funkci.

  • Záměr je jasný každému, kdo rozumí =default a =delete. Nemusíte rozumět pravidlům automatického generování speciálních členských funkcí.

Podobné idiomy existují pro vytváření uživatelsky definovaných typů, které nelze přizpůsobit, které lze dynamicky přidělit nebo které nelze dynamicky přidělit. Každý z těchto idiomů má před-C++11 implementace, které trpí podobnými problémy, a jsou podobně vyřešeny v jazyce C++11 jejich implementací z hlediska výchozích a odstraněných speciálních členských funkcí.

Explicitně výchozí funkce

Můžete výchozí libovolnou ze speciálních členských funkcí – explicitně uvést, že speciální členská funkce používá výchozí implementaci, definovat speciální členskou funkci s kvalifikátorem nepublikovaného přístupu nebo obnovit speciální členskou funkci, jejíž automatické generování bylo zabráněno jinými okolnostmi.

Speciální členovou funkci nastavíte tak, že ji deklarujete jako v tomto příkladu:

struct widget
{
  widget()=default;

  inline widget& operator=(const widget&);
};

inline widget& widget::operator=(const widget&) =default;

Všimněte si, že můžete výchozí speciální členskou funkci mimo tělo třídy, pokud je inlinable.

Vzhledem k výhodám výkonu triviálních speciálních členských funkcí doporučujeme, abyste při výchozím chování preferovali automatické generování speciálních členských funkcí před prázdnými těly funkcí. Můžete to provést buď explicitně ve výchozím nastavení speciální členské funkce, nebo tím, že ji nehlásíte (a také ne deklarujete další speciální členské funkce, které by zabránily jeho automatickému vygenerování.)

Odstraněné funkce

Můžete odstranit speciální členské funkce a běžné členské funkce a nečlenné funkce, aby se zabránilo jejich definování nebo zavolání. Odstranění speciálních členských funkcí poskytuje přehlednější způsob, jak kompilátoru zabránit v generování speciálních členských funkcí, které nechcete. Funkce musí být odstraněna, protože je deklarována; Později ji nelze odstranit způsobem, jakým lze funkci deklarovat a později ji nastavit jako výchozí.

struct widget
{
  // deleted operator new prevents widget from being dynamically allocated.
  void* operator new(std::size_t) = delete;
};

Odstranění normální členské funkce nebo nečlenných funkcí brání problematickému povýšení typů v tom, aby způsobovalo, že se nechtěná funkce volá. To funguje, protože odstraněné funkce se stále účastní rozlišení přetížení a poskytují lepší shodu než funkce, kterou lze volat po povýšení typů. Volání funkce se přeloží na konkrétnější (ale odstraněnou) funkci a způsobí chybu kompilátoru.

// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }

Všimněte si v předchozí ukázce, že volání call_with_true_double_only pomocí argumentu float způsobí chybu kompilátoru, ale volání call_with_true_double_only pomocí int argumentu by nebylo. V int takovém případě se argument upřednostní z doubleint verze funkce a úspěšně zavolá double verzi funkce, i když to nemusí být to, co máte v úmyslu. Chcete-li zajistit, aby jakékoli volání této funkce pomocí jiného než dvojitého argumentu způsobí chybu kompilátoru, můžete deklarovat verzi šablony odstraněné funkce.

template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.

void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.