Могут ли быть случаи, когда будет определяться понижение фактической базы до производного?

В общем случае это (очень хорошо заслуженный) Undefined Поведение для dowcast от (динамического) Base к одному из классов-получателей Derived

Очевидный UB

class Base
{
public:
    virtual void foo()
    { /* does something */ }

    int a;
}

class Derived : public Base
{
public:
    virtual void foo()
    { /* does something different */ }

    double b;
}

Base obj;
Derived derObj = *static_cast<Derived *>(&obj);  // <- here come the demons

В текущем подходе к реализации компиляторов здесь, очевидно, будут, по крайней мере, проблемы несогласованных значений в Vtable и b, содержащих значения мусора. Поэтому имеет смысл, что стандарт не определяет поведение нисходящего в этих условиях.

Не столь очевидный наивный случай

Но мне было любопытно узнать , если бы были определенные уступки этому правилу в конкретных случаях? Например:

class Base
{
public:
    void foo()
    { /* does something */ }

    int a = 1;
    double b = 2.;
}

class DerivedForInt : public Base
{
    int getVal()
    { return a }
}

Base obj;
DerivedForInt derObj = *static_cast<DerivedForInt *>(&obj);  // <- still an UB ?

Здесь мы можем легко представить, что компилятор делает правильные вещи. Но с точки зрения стандартного, все еще Undefined?

Изменить: static_cast - случайный выбор для иллюстрации, также интересно работать с другими приведениями!

Ответы

Ответ 1

Хорошо, я, вероятно, буду раскалывать куски за этот ответ...

Очевидно, как утверждают другие ответы, это поведение undefined, как указано в стандарте. Но если ваш класс Base имеет стандартный макет, а ваш класс DerivedForInt не добавляет новых членов данных, он будет иметь одинаковый (стандартный) макет.

В этих условиях ваш бросок не должен вызывать никаких проблем, даже если он является UB. Согласно одному из источников, по крайней мере безопасно сделать

DerivedForInt *derived = reinterpret_cast<DerivedForInt*>(&base.a);

Источники:

Что такое агрегаты и POD и как/почему они являются особенными?

POD и наследование в С++ 11. Является ли адрес адреса struct == первого участника?

Из второй ссылки:

Здесь определение из стандартного раздела 9 [класс]:

Класс стандартного макета - это класс, который:

     
  •   
  • не имеет нестатических членов данных типа нестандартного макета класса (или массива таких типов) или ссылки,  
  • не имеет виртуальных функций (10.3) и нет виртуальных базовых классов (10.1),  
  • имеет тот же контроль доступа (раздел 11) для всех нестатических членов данных,  
  • не имеет базовых классов нестандартной компоновки,  
  • либо не имеет нестатических членов данных в самом производном классе и не более одного базового класса с нестатическими членами данных, либо не имеет базовых классов с нестатическими членами данных, а  
  • не имеет базовых классов того же типа, что и первый нестатический элемент данных.  

И тогда свойство, которое вы хотите, будет гарантировано (раздел 9.2 [class.mem]):

Указатель на объект структуры стандартного макета, соответствующим образом преобразованный с использованием reinterpret_cast, указывает на его начальный член (или если этот элемент является битовым полем, а затем в единицу, в которой он находится) и наоборот.

Это действительно лучше старого требования, поскольку способность reinterpret_cast не теряется, добавляя нетривиальные конструкторы и/или деструктор.

Ответ 2

n3376 5.2.9/11

Значение типа "указатель на cv1 B", где B - тип класса, может быть преобразовано в указатель типа "указатель" to cv2 D ", где D - производный класс (раздел 10) из B, если действительное стандартное преобразование из" указателя на D "для" указателя на B" существует (4.10), cv2 является той же самой cv-квалификацией, что и более высокая cv-квалификация, чем cv1 и B не является ни виртуальным базовым классом D, ни базовым классом виртуального базового класса D. Значение нулевого указателя (4.10) преобразуется в значение нулевого указателя для типа адресата.

Если prvalue типа "указатель на cv1 B" указывает на B, который фактически является подобъектом объекта типа D, результирующий указатель указывает на охватывающий объект типа D. В противном случае результат приведения undefined.

Поскольку &obj не указывает на DerivedForInt его UB.

Ответ 3

Это поведение undefined, и я считаю, что это должно быть.

Почему это undefined

Как предоставлено @ForEveR в его ответе:

n3376 5.2.9/11

Значение типа "указатель на cv1 B", где B является типом класса, может быть преобразовано в prvalue типа "указатель на cv2 D", где D - это производный класс (раздел 10) из B

...

Если prvalue типа "указатель на cv1 B" указывает на B, который на самом деле является подобъектом объекта типа D, результирующий указатель указывает на охватывающий объект типа D. В противном случае результат литье undefined.

Почему это должно быть undefined

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

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