2013-06-29 2 views
2

Я попытался посмотреть, что произойдет на C++, если мы попытаемся «разбить» массив объектов аналогичным образом, мы можем попытаться сделать это на Java.В C++, можем ли мы оптимизировать массив, а затем попытаемся включить в него еще один подтип (вдохновленный Java ArrayStoreException)?

В Java мы можем иметь массив типа Double [], например, увеличить его до Number [] (поскольку Double является подклассом числа) и попытаться добавить другой подкласс Number to array, например, Integer. Код будет скомпилирован, но мы получим ArrayStoreException во время выполнения, потому что тип Integer будет проверен против фактического типа массива, который, как оказалось, будет Double, во время выполнения, и, конечно, будет несоответствие. Код может выглядеть так:

Double[] ds = new Double[12]; 
Number[] ns = ds; 
ns[0] = 2.3;    // OK 
ns[1] = new Integer(1); // compiles, but we have ArrayStoreException in runtime 

Так что я подумал - а как же C++? Можем ли мы попытаться выполнить тот же трюк? Что произойдет во время выполнения?

Вот код, который я пробовал, и выход.

#include <iostream> 

class A 
{ 
public: 
    A(int v = 0): val(v) {} 
    virtual void g() {std::cout << val << " in A\n";} 
    void setVal(int v) {val = v;} 
protected: 
    int val; 
}; 

class B : public A 
{ 
public: 
    virtual void g() {std::cout << val << " in B\n";} 
}; 

class C : public A 
{ 
public: 
    C(int v = 0): A(v) {} 
    virtual void g() {std::cout << val << " in C\n";} 
private: 
    int stuff[10]; 
}; 


void f(A* as) 
{ 
    as[1] = *(new A(12)); 
} 

void f2(A* as) 
{ 
    as[1] = *(new C(22)); 
} 

int main() 
{ 
    A* bs = new B[5]; 
    for (int i=0 ; i<5; ++i) 
    { 
     bs[i].setVal(i); 
     bs[i].g(); 
    } 

    std::cout << std::endl; 

    f(bs); 
    for (int i=0 ; i<5; ++i) 
    { 
     bs[i].g(); 
    } 

    std::cout << std::endl; 

    f2(bs); 
    for (int i=0 ; i<5; ++i) 
    { 
     bs[i].g(); 
    } 
} 

Выход:

0 in B 
1 in B 
2 in B 
3 in B 
4 in B 

0 in B 
12 in B 
2 in B 
3 in B 
4 in B 

0 in B 
22 in B 
2 in B 
3 in B 
4 in B 

Смотрите, как создание А или С, а затем скопировать его в массив B имеет данные от копирования (и, как и ожидалось, в случае C только данных, которые часть A скопирована - повреждение памяти после скопированного элемента), но выбран метод B, что означает, что vptr не должен быть скопирован.

Так что мои вопросы:

  • Я думаю, что vptr не копируется в операторе присваивания по умолчанию. Это так? Это единственная возможная причина, по которой у нас есть метод из B, но данные из объекта C?

  • Можем ли мы на самом деле придумать пример, который может привести к возникновению каких-то плохих или неожиданных событий, некоторая ошибка во время выполнения? Я предполагаю, что я имею в виду, что у меня есть массив Bs, но объект A или C в нем (не B или подтип B), объект, «чужой» B?

  • Возможно, язык C++ явно или неявно гарантирует некоторые комбинации его функций, которые этого не могут произойти (например, Java, когда он явно вызывает ArrayStoreException)?

UPD:

A* bs = new B[5]; 

я фактически изменил эту строку в последний момент, чтобы сделать акцент на выборе времени выполнения метода B (не должен был сделать это, очевидно, потому, что метод является виртуальным). Я изначально имел B* bs = new B[5];, и результат был таким же.

+1

C++-аналог Java 'Double [] ds' -' Double * ds [] ', то есть массив указателей **, а не ** массив объектов. –

ответ

1

Actualy, когда вы обмениваете A * bs = new B [5], вы делаете что-то неправильно. Это вызовет проблему, если размер элемента B не совпадает с размером элемента B. Поскольку вы можете пересобирать B * в A *, преобразование безопасно ... если бы существовал только один элемент.

Предположим, что A имеет длину 32 бит, а B - длину 64. Затем у вас будет странная вещь, когда вы просматриваете свой массив bs. (Bs [1] будет концом первого элемента B).

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

Итак:

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

  2. Да, просто добавьте некоторые элементы в B так, чтобы B был больше, чем A, и вы находитесь в прекрасном мире по неопределенному поведению. Вы также можете копировать часть A объекта C внутри объекта B, делающего это.

Nb: Если вы хотите управлять каким-то В и С в том же массиве, вы можете использовать A ** (или зЬй :: что угодно), а затем он будет безопасным. (Но вы, наверное, уже это знаете).

1

1) Да, vptr не копируется в операторе присваивания. Оператор присваивания не изменяет тип времени выполнения объекта.

2) A* bs = new B[5];является опасно. Что, если B содержит некоторые данные, которых нет в A? Затем sizeof(B) > sizeof(A), а доступ к элементам через bs[i] вызывает неопределенное поведение (ошибка сегментации в моей системе).

+0

2) Я фактически изменил эту строку в последний момент, чтобы подчеркнуть выбор времени выполнения B-метода (не должен был этого сделать, это очевидно, потому что метод является виртуальным). Я изначально имел «B * bs = новый B [5];» и результат был таким же. –