Чому функції, видалені C ++ 11, беруть участь у вирішенні перевантажень?


87

Чому C ++ 11 змушує функції " deleted" брати участь у вирішенні перевантажень ?
Чому це корисно? Або іншими словами, чому вони приховані, а не повністю видалені?


Одночасне запитання C ++ 11 "Нерухливий" тип .
Олаф Дієче

Див. Обгрунтування пропозиції open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2346.htm#delete
Джонатан

Відповіді:


114

Половина цілі = deleteсинтаксису полягає в тому, щоб перешкоджати людям викликати певні функції з певними параметрами. Це головним чином для запобігання неявних перетворень у певних конкретних сценаріях. Щоб заборонити певне перевантаження, він повинен брати участь у вирішенні перевантаження.

Відповідь, яку ви цитуєте, дає вам прекрасний приклад:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

Якщо повністю deleteвилучити функцію, це зробить = deleteсинтаксис таким:

struct onlydouble2 {
  onlydouble2(double);
};

Ви можете зробити це:

onlydouble2 val(20);

Це легальний C ++. Компілятор розгляне всі конструктори; жоден з них не приймає цілочисельний тип безпосередньо. Але один з них може прийняти це після неявного перетворення. Тож це буде називати це.

onlydouble val(20);

Це не є законним C ++. Компілятор розгляне всі конструктори, включаючи deleted. Він побачить точну відповідність через std::intmax_t(яка точно відповідатиме будь-якому цілочисельному літералу). Тож компілятор виділить його, а потім негайно видасть помилку, оскільки вибрав функцію deleted.

= deleteозначає "Я забороняю це", а не просто "Це не існує". Це набагато сильніше твердження.

Я запитував, чому стандарт C ++ говорить = delete означає "я забороняю це" замість "це не існує"

Це тому, що нам не потрібна спеціальна граматика, щоб сказати "цього не існує". Ми отримуємо це неявно, просто не оголошуючи конкретне "це", про яке йдеться. "Я забороняю це" являє собою конструкцію, якої неможливо досягти без спеціальної граматики. Тож ми отримуємо спеціальну граматику, щоб сказати "я забороняю це", а не інше.

Єдиною функціональністю, яку ви отримаєте, маючи явну граматику "цього не існує", було б запобігти тому, щоб хтось згодом оголосив, що вона існує. І це просто недостатньо корисно, щоб мати потребу в власній граматиці.

інакше не можна заявити, що конструктор копій не існує, і його існування може спричинити безглузді двозначності.

Конструктор копіювання - це спеціальна функція-член. Кожен клас завжди має конструктор копіювання. Так само, як у них завжди є оператор присвоєння копії, конструктор переміщення тощо.

Ці функції існують; питання полягає лише в тому, чи законно їх називати. Якщо ви спробували сказати, що це = deleteозначало, що вони не існують, тоді специфікація повинна була б пояснити, що означає, що функція не існує. Це не концепція, яку обробляє специфікація.

Якщо ви спробуєте викликати функцію, яка ще не оголошена / визначена, то компілятор помилиться. Але це буде помилка через невизначений ідентифікатор , а не через помилку "функція не існує" (навіть якщо ваш компілятор повідомляє про це таким чином). Всі різні конструктори називаються дозволом перевантаження, тому в цьому відношенні обробляється їхнє "існування".

У кожному випадку існує або функція, оголошена за допомогою ідентифікатора, або конструктор / деструктор (також оголошена за допомогою ідентифікатора, просто ідентифікатор типу). Перевантаження оператора приховує ідентифікатор за синтаксичним цукром, але він все ще є.

Специфікація С ++ не може обробляти концепцію "функції, яка не існує". Він може усунути невідповідність перевантаження. Це може впоратися з неоднозначністю перевантаження. Але воно не знає про те, чого там немає. Це = deleteвизначається з точки зору набагато корисніших "спроб назвати це невдалою", а не менш корисних "вдавати, що я ніколи не писав цього рядка".

І знову перечитайте першу частину. Ви не можете цього зробити за допомогою "функція не існує". Це ще одна причина, чому його визначають таким чином: оскільки одним із основних випадків використання = deleteсинтаксису є можливість змусити користувача використовувати певні типи параметрів, явно здійснювати приведення тощо. В основному, щоб запобігти неявним перетворенням типу.

Ваша пропозиція цього не зробить.


1
@Mehrdad: Якщо вам потрібні додаткові роз'яснення щодо того, чому = delete не працює так, як ви хочете, вам слід чіткіше зрозуміти точну семантику, яку, на вашу думку, слід мати = delete. Чи має це бути "вдавати, що я ніколи не писав цього рядка?" Або це має бути щось інше?
Nicol Bolas

1
Ні, я маю на увазі, я очікував мати = deleteна увазі "цей член не існує", що означатиме, що він не може брати участь у вирішенні перевантажень.
user541686

6
@Mehrdad: І це повертається до моєї початкової точки, саме тому я його опублікував: якщо = deleteмається на увазі "цей член не існує", то перший приклад, який я опублікував, не зможе перешкодити людям передавати цілі числа onlydoubleконструктору ' , оскільки onlydoubleвидалене перевантаження не існувало б . Він не братиме участі у вирішенні перевантажень, а отже, не завадить передавати цілі числа. Що становить половину точки = deleteсинтаксису: щоб мати можливість сказати: "Ви не можете неявно передати X цій функції".
Nicol Bolas

3
@ Mehrdad: За такою логікою, навіщо взагалі тобі це потрібно =delete? Зрештою, ми можемо сказати "не підлягає копіюванню", виконавши те саме те саме: оголосивши конструктор / призначення копіювання приватним. Також зауважте, що оголошення чогось приватного не робить його неможливим; код всередині класу все ще може його викликати. Тож це не те саме, що = delete. Ні, = deleteсинтаксис дозволяє нам робити щось дуже незручне та незрозуміле раніше набагато більш очевидним та розумним способом.
Нікол Болас

2
@Mehrdad: Оскільки останнє можливо : воно називається "не заявляй ". Тоді його не буде існувати. Щодо того, чому нам потрібен синтаксис, щоб приховати перевантаження, а не зловживати приватним ... Ви справді запитуєте, чому ми повинні мати засоби, щоб явно заявити щось, а не зловживати якоюсь іншою функцією, щоб отримати той самий ефект ? Просто тому, хто читає код, очевидніше, що відбувається. Це робить код легше зрозумілим для користувача та полегшує його написання, а також усуває проблеми в обхідному процесі. Нам також не потрібні лямбди.
Nicol Bolas

10

Робочий проект C ++ 02.11.2012 не дає обгрунтування цього правила, лише деякі приклади

8.4.3 Видалені визначення [dcl.fct.def.delete]
...
3 [ Приклад : Можна примусити ініціалізацію не за замовчуванням та неінтегральну ініціалізацію за допомогою

struct onlydouble {  
  onlydouble() = delete; // OK, but redundant  
  onlydouble(std::intmax_t) = delete;  
  onlydouble(double);  
};  

- кінцевий приклад ]
[ Приклад : Можна запобігти використанню класу в певних нових виразах, використовуючи видалені визначення оголошеного користувачем оператора нового для цього класу.

struct sometype {  
  void *operator new(std::size_t) = delete;  
  void *operator new[](std::size_t) = delete;  
};  
sometype *p = new sometype; // error, deleted class operator new  
sometype *q = new sometype[3]; // error, deleted class operator new[]  

- кінцевий приклад ]
[ Приклад : Можна зробити клас неможливим для копіювання, тобто лише переміщення, використовуючи видалені визначення конструктора копіювання та оператора присвоєння копії, а потім надаючи стандартні визначення конструктора переміщення та оператора присвоєння переміщення.

struct moveonly {  
  moveonly() = default;  
  moveonly(const moveonly&) = delete;  
  moveonly(moveonly&&) = default;  
  moveonly& operator=(const moveonly&) = delete;  
  moveonly& operator=(moveonly&&) = default;  
  ~moveonly() = default;  
};  
moveonly *p;  
moveonly q(*p); // error, deleted copy constructor  

- кінцевий приклад ]


4
Обгрунтування, здається, дуже чітке з прикладів, вам не здається?
Джессі Гуд,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.