C++ new[] в указателе базового класса, сбой при доступе к массиву
-
06-07-2019 - |
Вопрос
Когда я выделяю один объект, этот код работает нормально.Когда я пытаюсь добавить синтаксис массива, происходит сбой.Почему это?Моя цель здесь — скрыть от внешнего мира тот факт, что класс c использует внутри себя объекты b.Я выложил программу кодовая панель для тебя, чтобы играть.
#include <iostream>
using namespace std;
// file 1
class a
{
public:
virtual void m() { }
virtual ~a() { }
};
// file 2
class b : public a
{
int x;
public:
void m() { cout << "b!\n"; }
};
// file 3
class c : public a
{
a *s;
public:
// PROBLEMATIC SECTION
c() { s = new b[10]; } // s = new b;
void m() { for(int i = 0; i < 10; i++) s[i].m(); } // s->m();
~c() { delete[] s; } // delete s;
// END PROBLEMATIC SECTION
};
// file 4
int main(void)
{
c o;
o.m();
return 0;
}
Решение
Одна проблема заключается в том, что выражение s [i]
использует арифметику указателей для вычисления адреса нужного объекта. Поскольку s
определяется как указатель на a
, результат является правильным для массива из a
s и неверным для массива из b код> s. Динамическое связывание, обеспечиваемое наследованием, работает только для методов, и ничего больше (например, нет виртуальных элементов данных, нет виртуального
sizeof
). Таким образом, при вызове метода s [i] .m ()
указатель this
устанавливается на то, что будет i
th a
объект в массиве. Но так как на самом деле массив является одним из b
, он заканчивается (иногда) указанием где-то в середине объекта, и вы получаете ошибку (возможно, когда программа пытается получить доступ к vtable объекта) ). Возможно, вам удастся устранить проблему путем виртуализации и перегрузки оператора operator [] ()
. (Однако я не продумал, чтобы увидеть, сработает ли это на самом деле.)
Другая проблема - это delete
в деструкторе по тем же причинам. Вы также можете виртуализировать и перегружать его. (Опять же, случайная идея, которая пришла мне в голову. Может не сработать.)
Конечно, приведение (как предложено другими) тоже будет работать.
Другие советы
Создание массива из 10 b
с new
а затем присваивая его адрес a*
просто напрашивается на неприятности.
Не обрабатывайте массивы полиморфно.
Для получения дополнительной информации см. ARR39-CPP.Не обрабатывайте массивы полиморфно, в разделе 06.Массивы и STL (ARR) принадлежащий CERT C++ Стандарт безопасного кодирования.
У вас есть массив типа " b " не типа "а" и вы назначаете его указателю типа a. Полиморфизм не переносится в динамические массивы. Р>
a* s
к
b* s
и вы увидите, что это начнет работать.
Только еще не привязанные указатели могут быть обработаны полиморфно. Подумай об этом
a* s = new B(); // works
//a* is a holder for an address
a* s = new B[10]
//a* is a holder for an address
//at that address are a contiguos block of 10 B objects like so
// [B0][B2]...[B10] (memory layout)
когда вы перебираете массив с помощью s, подумайте о том, что используется
s[i]
//s[i] uses the ith B object from memory. Its of type B. It has no polymorphism.
// Thats why you use the . notation to call m() not the -> notation
до того, как вы преобразовали в массив, который вы только что имели
a* s = new B();
s->m();
s здесь просто адрес, а не статический объект, как s [i]. Просто адрес s все еще может быть динамически связан. Что у с? Кто знает? Что-то по адресу s.
См. отличный ответ Ari ниже для получения дополнительной информации о том, почему это также не имеет смысла с точки зрения расположения массивов в стиле C.
Каждый экземпляр B содержит как элемент данных X, так и " vptr " (указатель на виртуальную таблицу). Р>
Каждый экземпляр A содержит только «vptr»
Таким образом, sizeof (a)! = sizeof (b).
Теперь, когда вы сделаете это: " S = new b [10] " вы кладете в память 10 экземпляров b в необработанном виде, а S (который имеет тип a *) получает начало, что и необработанные данные.
в методе C :: m () вы указываете компилятору перебирать массив " a " (потому что s имеет тип a *), НО , s фактически указывает на массив " b " ;. Поэтому, когда вы вызываете s [i], то, что фактически делает компилятор, это "s + i * sizeof (a)" компилятор скачет в единицах «а» вместо единиц "b" и так как a и b не имеют одинаковый размер, вы получите много mambojumbo.
Я нашел обходной путь на основе ваших ответов. Это позволяет мне скрыть особенности реализации, используя слой косвенности. Это также позволяет мне смешивать и сопоставлять объекты в моем массиве. Спасибо!
#include <iostream>
using namespace std;
// file 1
class a
{
public:
virtual void m() { }
virtual ~a() { }
};
// file 2
class b : public a
{
int x;
public:
void m() { cout << "b!\n"; }
};
// file 3
class c : public a
{
a **s;
public:
// PROBLEMATIC SECTION
c() { s = new a* [10]; for(int i = 0; i < 10; i++) s[i] = new b(); }
void m() { for(int i = 0; i < 10; i++) s[i]->m(); }
~c() { for(int i = 0; i < 10; i++) delete s[i]; delete[] s; }
// END PROBLEMATIC SECTION
};
// file 4
int main(void)
{
c o;
o.m();
return 0;
}