Полиморфные объекты в стеке?

В предыдущем вопросе Я цитировал Stroustrup о том, почему общий класс Object для всех классов проблематичен в С++. В этой цитате есть утверждение:

Используя универсальный базовый класс, стоимость: объекты должны быть выделены в виде кучи быть полиморфным;

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

Однако комментатор указал, что это, вероятно, не так, и в ретроспективе я не могу найти веских причин, почему это должно быть правдой. Короткий тестовый пример дает ожидаемый результат "VDerived:: f()".

struct VBase
{
    virtual void f()    { cout<<"VBase::f()"<<endl; }
};

struct VDerived:VBase
{
    void f()  { cout<<"VDerived::f()"<<endl; }
};

void test(VBase& obj){
    obj.f();
}

int main(int argc, char** argv) {
    VDerived obj;
    test(obj);
    return 0;
}

Конечно, если формальный аргумент для теста был test(VBase obj), то случай был бы совершенно другим, но это не было бы аргументом стека против кучи, а скорее копией семантики.

Является ли Бьярне ошибочным или я что-то пропустил?

Ответ 1

Похож на полиморфизм для меня.

Полиморфизм в С++ работает, когда у вас есть косвенность; то есть либо pointer-to-T, либо reference-to-T. Где хранится T, не имеет значения.

Бьярне также ошибается, говоря, что "куча-выделенная" технически неточна.

(Примечание: это не означает, что универсальный базовый класс "хорош"!)

Ответ 2

Я думаю, что Bjarne означает, что obj, или, точнее, объект, на который он указывает, не может быть легко основан на стеках в этом коде:

int f(int arg) 
{ 
    std::unique_ptr<Base> obj;    
    switch (arg) 
    { 
    case 1:  obj = std::make_unique<Derived1      >(); break; 
    case 2:  obj = std::make_unique<Derived2      >(); break; 
    default: obj = std::make_unique<DerivedDefault>(); break; 
    } 
    return obj->GetValue(); 
}

У вас не может быть объекта в стеке, который изменяет его класс, или изначально не уверен, какой именно класс принадлежит ему.

(Конечно, чтобы быть действительно педантичным, можно было бы выделить объект в стеке, используя размещение-новое на alloca -распределенном пространстве. Тот факт, что существуют сложные обходные пути, находится здесь, хотя.)

Следующий код также не работает, как можно было бы ожидать:

int f(int arg) 
{ 
    Base obj = DerivedFactory(arg); // copy (return by value)
    return obj.GetValue();
}

Этот код содержит ошибку среза объекта: пространство стека для obj имеет размер как экземпляр класса Base; когда DerivedFactory возвращает объект производного класса, который имеет некоторые дополнительные члены, он не будет скопирован в obj, который делает obj недействительным и непригодным для использования в качестве производного объекта (и, возможно, даже непригодным для использования в качестве базового объекта).

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


Конечно, любой полностью построенный производный объект, где бы он ни хранился, может действовать как базовый объект и, следовательно, действовать полиморфно. Это просто следует из отношения is-a, которое объекты унаследованных классов имеют с базовым классом.

Ответ 3

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

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

PS (комментарий капитана Жирафа): Было бы бесполезно иметь функцию

f(object o)

что означает, что общая функция должна быть

f(object &o)

И это будет означать, что объект должен быть полиморфным, что, в свою очередь, означает, что его нужно будет выделять отдельно, что часто будет означать на куче, хотя оно может быть в стеке. С другой стороны, теперь у вас есть:

template <typename T>
f(T o) // see, no reference

который в большинстве случаев становится более эффективным. Это особенно касается коллекций, где, если бы все, что у вас было, было вектором таких базовых объектов (как это делает Java), вам придется выделять все объекты отдельно. Что было бы большими накладными расходами, особенно учитывая низкую производительность распределителя в момент создания С++ (у Java все еще есть преимущество в этом, потому что копирование сборщика мусора более эффективно, а С++ не может его использовать).

Ответ 4

Утверждение Bjarne неверно.

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

Для этого не имеет значения, является ли экземпляр распределенным по куче или стеку, если он доступен через ссылку или указатель (T& instance или T* instance).

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

Ответ 5

Я думаю, что он шел по строкам, не имея возможности хранить его в переменной с типичным типом. Вы правы, говоря, что можете хранить его в стеке, если это производного типа, потому что в этом нет ничего особенного; концептуально, он просто хранит данные класса и его производных + a vtable.

edit: Хорошо, теперь я смущен, пересматриваю пример. Похоже, вы можете быть прямо сейчас...

Ответ 6

Я думаю, что дело в том, что это не "действительно" полиморфно (что бы это ни значило: -).

Вы можете написать свою тестовую функцию следующим образом

template<class T>
void test(T& obj)
{
    obj.f();
}

и все равно будет работать, имеют ли классы виртуальные функции или нет.

Ответ 7

Вам что-то не хватает. Ваш VDerived объект obj не работает полиморфно, он ведет себя точно как объект VDerived.