Может ли объяснитель конструктора якобы быть проигнорирован из-за формы инициализации? [Дубликат]

Я не помню, откуда я получил этот материал, но мог бы лучше понять обещания.

Обещания не являются обратными вызовами. Обещание представляет собой будущий результат асинхронной операции. Конечно, записывая их так, как вы, вы получаете небольшую выгоду. Но если вы пишете их так, как они предназначены для использования, вы можете написать асинхронный код таким образом, который напоминает синхронный код, и его гораздо проще выполнить: ПРЕИМУЩЕСТВА 1. Читаемость по обратным вызовам 2. Легко ловить ошибки. 3. Одновременные обратные вызовы

ПРЕИМУЩЕСТВА Обещания обеспечивают более сжатый и понятный способ представления последовательных асинхронных операций в javascript. Они представляют собой отличный синтаксис для достижения того же эффекта, что и обратные вызовы. Преимуществом является повышенная читаемость. Что-то вроде этого

aAsync()   .then(bAsync)   .then(cAsync)   .done(finish);

гораздо читаемо, чем эквивалент передачи каждой из этих отдельных функций в качестве обратных вызовов, таких как

Async(function(){     return bAsync(function(){         return cAsync(function(){             finish()         })     }) }); //-------------------------------------------- api().then(function(result){      return api2(); }).then(function(result2){     return api3(); }).then(function(result3){      // do work });

2. Легко ловить ошибки. Конечно, не намного меньше кода, но гораздо более удобочитаемым. Но это еще не конец. Давайте обнаружим истинные преимущества: что, если вы хотите проверить какую-либо ошибку на любом из шагов? Было бы чертовски сделать это с помощью обратных вызовов, но с обещаниями - кусок торта:

api().then(function(result){    return api2(); }).then(function(result2){     return api3(); }).then(function(result3){      // do work }).catch(function(error) {       //handle any error that may occur before this point }); /* Pretty much the same as a try { ... } catch block. Even better: */ api().then(function(result){     return api2(); }).then(function(result2){     return api3(); }).then(function(result3){      // do work }).catch(function(error) {      //handle any error that may occur before this point }).then(function() {      //do something whether there was an error or not      //like hiding an spinner if you were performing an AJAX request. });

2. Легко ловить ошибки. Что делать, если эти 3 вызова api, api2, api3 могут выполняться одновременно (например, если они были вызовами AJAX), но вам нужно было дождаться трех? Без обещаний вам нужно создать какой-то счетчик. С обещаниями, использующими нотацию ES6, это еще один кусок торта и довольно аккуратный:

Promise.all([api(), api2(), api3()]).then(function(result) {     //do work. result is an array contains the values of the three fulfilled promises. }).catch(function(error) {     //handle the error. At least one of the promises rejected. });

Надеюсь, вы увидите обещания в новом свете.

21
задан Barry 5 January 2016 в 23:06
поделиться

2 ответа

Насколько я могу судить, это ошибка clang.

Инициализация списка копирования имеет довольно неинтуитивное поведение: он считает явные конструкторы жизнеспособными до тех пор, пока разрешение перегрузки не будет полностью завершено, но может отклонить результат перегрузки, если выбран явный конструктор. Формулировка в проекте post-N4567 [over.match.list] p1

В инициализации списка копий, если выбран конструктор explicit, инициализация плохо сформирована. [ Примечание: Это отличается от других ситуаций (13.3.1.3, 13.3.1.4), где рассматриваются только конструкторы преобразования для инициализации копирования. Это ограничение применяется только в том случае, если эта инициализация является частью окончательного результата разрешения перегрузки. - end note ]


clang HEAD принимает следующую программу:

#include <iostream>
using namespace std;

struct String1 {
    explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
    String2(const char*) { cout << "String2\n"; }
};

void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }

int main()
{
    //f1( {"asdf"} );
    f2( {"asdf"} );
    f( {"asdf"} );
}

Которая, за исключением комментирования звоните в f1, прямо из N2532 Bjarne Stroustrup - Унифицированная инициализация , глава 4. Благодаря Johannes Schaub для показа мне эту статью на std-обсуждение .

В той же главе содержится следующее объяснение:

Реальное преимущество explicit в том, что оно делает ошибку f1("asdf") ошибкой. Проблема в том, что разрешение перегрузки «предпочитает» конструкторы не explicit, так что f("asdf") вызывает f(String2). Я считаю, что разрешение f("asdf") меньше идеала, потому что автор String2, вероятно, не имел целью разрешить двусмысленности в пользу String2 (по крайней мере, не в каждом случае, когда явные и неявные конструкторы происходят так) и писатель String1, конечно, этого не делал. Правило благоприятствует «неаккуратным программистам», которые не используют explicit.


Насколько мне известно, N2640 - Списки инициализаторов - Альтернативный механизм и обоснование - последняя статья, которая включает в себя обоснование такого разрешения перегрузки; его преемник N2672 был проголосован в черновик C ++ 11.

Из его главы «Значение явного»:

Первый подход для того чтобы сделать пример плохо сформированным, требуется, чтобы все конструкторы (явные и неявные) рассматривались для неявных преобразований, но если выбран явный конструктор, эта программа плохо сформирована. Это правило может ввести свои собственные сюрпризы; например:

struct Matrix {
    explicit Matrix(int n, int n);
};
Matrix transpose(Matrix);

struct Pixel {
    Pixel(int row, int col);
};
Pixel transpose(Pixel);

Pixel p = transpose({x, y}); // Error.

Второй подход заключается в том, чтобы игнорировать явные конструкторы при поиске жизнеспособности неявного преобразования, но включать их при фактическом выборе конструктора преобразования: если явный конструктор заканчивается выбранный, программа плохо сформирована. Этот альтернативный подход позволяет последнему примеру (Pixel-vs-Matrix) работать как ожидалось (выбран transpose(Pixel)), в то время как исходный пример («X x4 = { 10 };») плохо сформирован.

Хотя в этом документе предлагается использовать второй подход, его формулировка представляется ошибочной - в моей интерпретации формулировки она не приводит к поведению, изложенному в обоснованной части статьи. Формулировка пересмотрена в N2672, чтобы использовать первый подход, но я не мог найти никакого обсуждения о том, почему это было изменено.


Конечно, немного больше формулировок, связанных с инициализацией переменной, как в OP, но учитывая разницу в поведении между clang и gcc, то же самое для первой программы-образца в моем ответе, я думаю, что это касается основных моментов.

8
ответ дан Community 21 August 2018 в 22:21
поделиться

Это не полный ответ, хотя он слишком длинный, как комментарий. Я постараюсь предложить контрпример к вашим рассуждениям, и я готов смотреть вниз, потому что я далек от того, чтобы быть уверенным. В любом случае, давайте попробуем! : -)

Это следует из приведенного примера:

struct A {
    A(int, int) { }
};

struct B {
    B(A) { }
    explicit B(int, int ) { }
};

int main() {
    B paren({1, 2});
}

В этом случае утверждение {1, 2}, по-видимому, дает два решения:

  • с помощью B(A), поскольку A(int, int) не является явным и, следовательно, это разрешено, и это фактически первый кандидат
  • по той же причине, что и выше, его можно интерпретировать как B{B(A{1,2})} (ну, позвольте мне оскорбить нотацию, чтобы дать вам представление и что я имею в виду), то есть {1,2} позволяет построить временный объект B, который используется сразу после аргумента для конструктора copy / move, и это снова разрешено, потому что задействованные конструкторы не являются явными

. Последний объясняет второй и третий кандидаты.

Имеет ли смысл? Я готов удалить ответы до тех пор, пока вы объясните мне, что не так в моих рассуждениях. : -)

0
ответ дан skypjack 21 August 2018 в 22:21
поделиться
  • 1
    Второй разрывает однопользовательское правило преобразования. – T.C. 6 January 2016 в 01:19
  • 2
    Я вам доверяю, но можете ли вы быть более подробным? Я подозреваю, что узнаю что-то новое здесь. Спасибо. – skypjack 6 January 2016 в 01:28
  • 3
    Стандартизация разбросана по всему месту, но основная идея заключается в том, что при формировании последовательности преобразования вы можете использовать не более одного пользовательского преобразования. – T.C. 6 January 2016 в 01:36
  • 4
    @ T.C. Получил это, он нарушает правило, потому что неявно объявленные копии ctors и move ctors тоже преобразуют конструкторы , правильно? – skypjack 6 January 2016 в 01:44
  • 5
    @ T.C. Я по-прежнему не прав, в документации написано конструктор, который не объявлен с явным спецификатором и , который может быть вызван с помощью одного параметра (до C ++ 11) называется конструктором преобразования . . Это не тот случай. Мне не удается понять, почему это нарушает это правило ... :-( ... Извините, я не хочу вас беспокоить, но я хотел бы понять. – skypjack 6 January 2016 в 01:54
Другие вопросы по тегам:

Похожие вопросы: