Comma-оператор с типом

Я изучал Общая реализация неинтрузивных интеллектуальных указателей. У меня есть некоторая путаница в разделе 4. Один оператор

выражение, предоставленное в качестве аргумента для оператора typeid, оценивается только в том случае, если тип результата является lvalue типа полиморфного класса.

И соответствующий примерный код:

template<typename T>
  void* startOfObject(T* p) {
  void* q=static_cast<void*>(p);
  typeid(q=dynamic_cast<void*>(p),*p); // This line
  return q;
}

AFAIU, это означает, что q=dynamic_cast<void*>(p) будет оцениваться, если тип результата является lvalue типа полиморфного класса. Результат означает результат оценки dynamic_cast<void*>(p) (я думаю), поэтому dynamic_cast должен применяться в любом случае. В статьях говорится (как я понимаю), что если p не является полиморфным, то dynamic_cast не будет применяться, но почему? Прежде чем применять его, как можно узнать, является ли результат полиморфным или нет? Будет полезно, если кто-то подробно описывает, как будет выполняться полный оператор.

Другим утверждением является

Также существует проблема, если p является NULL - typeid будет вызывать std:: bad cast.

Проблема, которую я вижу, связана с де-ссылкой, если p - NULL, а не с typeid (хотя это может вызвать bad_typeid, но это не из-за кастинга). dynamic_cast вернет указатель NULL типа void*, если p - NULL, а typeid должен иметь возможность выводить информацию о типе. Это опечатка, или я чего-то не хватает?

Ответы

Ответ 1

Это фантастический трюк, чтобы написать по существу следующий код

if (T is polymorphic)
  return dynamic_cast<void*>(p);
else
  return static_cast<void*>(p);

Используемый трюк заключается в том, что typeid(expr) оценивается одним из двух способов. Если компилятор определяет, что expr имеет неполиморфный тип, он идет вперед и использует свой статический тип. Но если expr имеет динамический тип, он оценивает expr во время выполнения. Поэтому присваивание перед оператором запятой оценивается тогда и только тогда, когда *p после запятой является полиморфной.

Нулевой случай является сложным по этой причине. Если T не является полиморфным, то typeid(*p) заменяется компилятором во время компиляции, а нулевой указатель времени выполнения вообще не имеет значения. Если T является полиморфным, применяется специальная обработка замещений нулевого указателя, и эта специальная обработка заявляет, что исключение std::bad_typeid выбрано.

Ответ 2

Оператор запятой имеет лево-правую ассоциативность и оценивается слева направо. Это означает, что результат выражения q=dynamic_cast<void*>(p),*p равен *p. Таким образом, динамический кастинг оценивается только в том случае, если тип *p является полиморфным.

Что касается проблемы NULL, то стандартное состояние:

Когда typeid применяется к выражению lvalue, тип которого является полиморфный тип класса (10.3), результат относится к объекту type_info представляющий тип самого производного объекта (1.8) (т.е. динамический тип), к которому относится значение lvalue. Если выражение lvalue равно полученных путем применения унарного * оператора к указателю и указателю является значением нулевого указателя (4.10), выражение typeid выбрасывает исключение bad_typeid (18.5.3).

Ответ 3

Объяснение

Так как С++ является статически типизированным языком, тип каждого выражения известен во время компиляции, даже в случаях, связанных с полиморфным поведением.

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

Если компилятор видит, что expr в typeid(expr) не даст значение типа полиморфного класса, он просто сделает expr "unevaluated", что эквивалентно тому, что он не выполняет его во время выполнения.


!!

Важно отметить, что expr все равно будет действительным, мы не можем использовать typeid, чтобы потенциально игнорировать плохо сформированное выражение; диагностика компилятора должна быть выпущена, если выражение плохо сформировано.

Просто потому, что expr будет "unevaluated", это не значит, что мы можем содержать плохо сформированное подвыражение.

struct A { }; // not polymorphic

...

A * ptr = ...;
typeid (dynamic_cast<void*> (ptr), *ptr); // ill-formed

Так как ptr не является указателем на полиморфный класс, мы не можем его использовать в dynamic_cast<T>, где T = void*, как указано в [expr.dynamic.cast]p6.


Разыменование потенциального nullptr?

Это сделает исключение typeid, поэтому оно не даст поведения undefined, но нужно быть готовым обработать исключение, если вы используете такую ​​реализацию.

5.2.8p2 Идентификация типа [expr.typeid]

(...) Если выражение glvalue получается путем применения унарного оператора * к указателю, а указатель - значение нулевого указателя (4.10), выражение typeid генерирует исключение (15.1) типа, будет соответствовать обработчику типа std::bad_typeid exception (18.8.3)


Разъяснение

Это означает, что q = dynamic_cast (p) будет оцениваться, если тип результата является lvalue типа полиморфного класса. Результат означает результат оценки dynamic_cast (p) (я думаю).

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

Примечание: "Полное выражение" относится к ... в typeid(...), в вашем случае; q=dynamic_cast<void*>(p),*p.Суб >


Оператор Comma

Оператор запятой возьмет два операнда expr1 и expr2, он начнет вычислять expr1 и затем отбросит это значение, после чего он будет вычислять expr2 и выдает значение этого выражения.


Совмещение

Это означает, что результирующий тип использования оператора запятой выглядит так, как если бы он состоял только из правого выражения, а в следующей строке компилятор проверяет, чтобы результат expr2 был типом, который является полиморфный класс.

typeid (expr1, expr2)

typeid (q=dynamic_cast<void*>(p), *p)

// expr1 = q=dynamic_cast<void*>(p)
// expr2 = *p

Примечание. В С++ 03 результирующий тип должен был быть полиморфным lvalue, в С++ 11 это было изменено на glvalues ​​полиморфного тип класса.

Ответ 4

Результат означает результат вычисления dynamic_cast (p) (I предположим), поэтому dynamic_cast должен применяться в любом случае. статьи (как я понимаю), что если p не является полиморфным, тогда dynamic_cast не будет применяться

dynamic_cast не будет применяться, если какой-либо из задействованных типов не имеет полиморфного отношения друг к другу, за исключением возможности применить к void*.

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

error: не может dynamic_cast 'p' (типа 'struct A *') для ввода 'void *' (тип источника не является полиморфным)

или когда A является полиморфным, вы увидите

предупреждение: вычисленное значение не используется [-Унимое значение]

для

typeid(q=dynamic_cast<void*>(p),*p); // this line

Итак, на мой взгляд, вся строка не имеет никакого смысла, кроме как, возможно, предотвратить компиляцию, когда тип источника не является полиморфным.

Что касается встроенного оператора запятой, который используется здесь: он оценивается слева направо, всегда безоговорочно, и его результатом является последний элемент. То есть результат эквивалентен

typeid(*p);

который не используется и, следовательно, довольно бесполезная конструкция. Будет ли он компилироваться для не полиморфных типов источников в dynamic_cast, тогда он вообще не будет оцениваться во время выполнения, поскольку тип *p известен во время компиляции.

ISO14882: 2001 (e) §5.18-1

Пара выражений, разделенных запятой, оценивается слева направо; левое выражение является выражением отбрасываемого значения (п. 5). 83 Каждый вычисление значения и побочный эффект, связанный с левым выражением секвенируется перед вычислением каждого значения и связанного с ним побочного эффекта с правильным выражением. Тип и значение результата - это тип и значение правильного операнда; результат имеет одинаковую ценность как его правый операнд и является битовым полем, если его правый операнд это glvalue и бит-поле.

Относительно проблемы nullptr

Также существует проблема, если p является NULL - typeid будет вызывать std:: bad cast.

В стандарте есть специальный случай, чтобы сделать это не undefined:

Если выражение glvalue получается путем применения унарного оператора * к a указатель 68 а указатель - значение нулевого указателя (4.10), выражение typeid вызывает std:: bad_typeid исключение (18.7.3).