Полиморфные объекты в стеке? - PullRequest
29 голосов
/ 05 мая 2011

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

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

Я действительно не смотрелдважды на него, и с момента его на домашней странице 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), случай был бы совершенно другим, но это был бы не аргумент стека против кучи, а скорее семантика копирования.

Бьярне не в порядке или я что-то здесь упускаю?

Ответы [ 7 ]

12 голосов
/ 05 мая 2011

Мне кажется, что это полиморфизм.

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

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

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

6 голосов
/ 29 августа 2013

Я думаю, что Бьярне означает, что 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 , которое объекты наследуемых классов имеют с базовым классом.

2 голосов
/ 06 мая 2011

Неверное утверждение Бьярне.

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

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

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

2 голосов
/ 05 мая 2011

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

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

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

f(object o)

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

f(object &o)

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

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

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

1 голос
/ 05 мая 2011

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

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

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

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

1 голос
/ 05 мая 2011

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

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

0 голосов
/ 05 мая 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...