Подтвердить что ты не робот

Могу ли я иметь полиморфные контейнеры со значениями семантики в С++?

Как правило, я предпочитаю использовать значение, а не семантику указателя в С++ (т.е. используя vector<Class> вместо vector<Class*>). Обычно небольшая потеря производительности более чем компенсируется тем, что не нужно запоминать удаленные динамически выделенные объекты.

К сожалению, коллекции значений не работают, если вы хотите хранить множество типов объектов, все из которых выведены из общей базы. См. Пример ниже.

#include <iostream>

using namespace std;

class Parent
{
    public:
        Parent() : parent_mem(1) {}
        virtual void write() { cout << "Parent: " << parent_mem << endl; }
        int parent_mem;
};

class Child : public Parent
{
    public:
        Child() : child_mem(2) { parent_mem = 2; }
        void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; }

        int child_mem;
};

int main(int, char**)
{
    // I can have a polymorphic container with pointer semantics
    vector<Parent*> pointerVec;

    pointerVec.push_back(new Parent());
    pointerVec.push_back(new Child());

    pointerVec[0]->write(); 
    pointerVec[1]->write(); 

    // Output:
    //
    // Parent: 1
    // Child: 2, 2

    // But I can't do it with value semantics

    vector<Parent> valueVec;

    valueVec.push_back(Parent());
    valueVec.push_back(Child());    // gets turned into a Parent object :(

    valueVec[0].write();    
    valueVec[1].write();    

    // Output:
    // 
    // Parent: 1
    // Parent: 2

}

Мой вопрос: могу ли я иметь свой торт (семантика значений) и есть его (полиморфные контейнеры)? Или мне нужно использовать указатели?

4b9b3361

Ответ 1

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

Одно разумное решение - хранить безопасные смарт-указатели контейнера. Обычно я использую boost:: shared_ptr, который безопасен для хранения в контейнере. Обратите внимание, что std:: auto_ptr не является.

vector<shared_ptr<Parent>> vec;
vec.push_back(shared_ptr<Parent>(new Child()));

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

Ответ 2

Да, вы можете.

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

Ответ 3

Я просто хотел указать, что vector <Foo> обычно более эффективна, чем вектор < Foo * > . В векторе <Foo> , все Foos будут смежными друг с другом в памяти. Предполагая, что холодный TLB и кеш, первое чтение добавит страницу в TLB и потянет кусок вектора в кеши L #; последующие чтения будут использовать теплый кеш и загруженный TLB, с случайными промахами кеша и менее частыми ошибками TLB.

Контрастируйте это с помощью вектора < Foo * > : Когда вы заполняете вектор, вы получаете Foo * из вашего распределителя памяти. Предполагая, что ваш распределитель не очень умный, (tcmalloc?) Или вы медленно заполняете вектор, местоположение каждого Foo, вероятно, будет далеко от других Foos: возможно, всего на сотни байтов, может быть, на мегабайтах.

В худшем случае, когда вы просматриваете вектор < Foo * > и разыменовывая каждый указатель, вы понесете ошибку TLB и пропущен кеш - это будет намного медленнее, чем если бы у вас был вектор <Foo> . (Ну, в самом худшем случае каждый Foo выгружается на диск, и каждое чтение берет на себя поиск диска() и read(), чтобы переместить страницу обратно в ОЗУ.)

Итак, продолжайте использовать вектор <Foo> когда это необходимо.: -)

Ответ 4

Вы также можете рассмотреть boost:: any. Я использовал его для гетерогенных контейнеров. При чтении значения обратно вам нужно выполнить any_cast. Это приведет к ошибке bad_any_cast, если он не сработает. Если это произойдет, вы можете перехватить и перейти к следующему типу.

Я полагаю, что он бросит bad_any_cast, если вы попытаетесь использовать any_cast для производного класса на его основе. Я попробовал:

  // But you sort of can do it with boost::any.

  vector<any> valueVec;

  valueVec.push_back(any(Parent()));
  valueVec.push_back(any(Child()));        // remains a Child, wrapped in an Any.

  Parent p = any_cast<Parent>(valueVec[0]);
  Child c = any_cast<Child>(valueVec[1]);
  p.write();
  c.write();

  // Output:
  //
  // Parent: 1
  // Child: 2, 2

  // Now try casting the child as a parent.
  try {
      Parent p2 = any_cast<Parent>(valueVec[1]);
      p2.write();
  }
  catch (const boost::bad_any_cast &e)
  {
      cout << e.what() << endl;
  }

  // Output:
  // boost::bad_any_cast: failed conversion using boost::any_cast

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

Ответ 5

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

Так что делать? Ну, есть несколько милых опций, но большинство из них сводится к вариантам по одной из нескольких тем или их комбинаций: сбор или придумывание подходящего умного указателя, игра с шаблонами или шаблонами шаблонов каким-то умным способом, с использованием общего интерфейса для сдержанных который обеспечивает привязку для реализации двойной диспетчеризации для каждого пользователя.

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

В прошлом я некоторое время пытался представить, как использовать Virtual Proxy/Envelope-Letter/этот симпатичный трюк со ссылками на подсчитанные указатели, чтобы выполнить что-то вроде основы для семантического программирования значений в С++.

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

Ответ 6

Взгляните на static_cast и reinterpret_cast
На языке программирования С++, 3-е изд., Bjarne Stroustrup описывает его на стр. 130. Там весь раздел по этому вопросу в главе 6.
Вы можете переделать свой родительский класс в класс Child. Это требует, чтобы вы знали, когда каждый из них. В книге доктор Страуструп рассказывает о различных методах, чтобы избежать этой ситуации.

Не делай этого. Это отрицает полиморфизм, который вы пытаетесь достичь в первую очередь!

Ответ 7

Просто добавьте одну вещь ко всем 1800 ИНФОРМАЦИЯ уже сказано.

Возможно, вы захотите взглянуть на "Более эффективный С++" Скотта Майерса "Пункт 3: никогда не обрабатывать массивы полиморфно" в порядке чтобы лучше понять эту проблему.

Ответ 8

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

Это идея, которая может удовлетворить ваши потребности.