Могу ли я иметь полиморфные контейнеры с семантикой значений в C ++ 11? - PullRequest
6 голосов
/ 10 сентября 2010

Это продолжение связанной статьи , в которой задан вечный вопрос:

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

Вопрос был задан слегка некорректно.Это должно было быть больше похоже на:

Могу ли я иметь контейнеры STL базового типа, хранимые по значению, в которых элементы проявляют полиморфное поведение?

Если вы спрашиваетевопрос с точки зрения C ++, ответ «нет».В какой-то момент вы нарежете объекты, хранящиеся по значению.

Теперь я снова задаю вопрос, но строго в терминах C ++ 11.Можно ли с учетом изменений в языке и стандартных библиотеках хранить полиморфные объекты по значению в контейнере STL?

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

Рассмотрим, если вы (из связанной статьи), как базовый пример C ++:

#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

}

Ответы [ 3 ]

8 голосов
/ 11 сентября 2010

Вы, конечно, не можете иметь полиморфный массив (или vector).Требование, чтобы элементы массива хранились непрерывно в памяти, принципиально несовместимо с тем фактом, что разные типы производных классов могут иметь разные размеры.

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

4 голосов
/ 11 сентября 2010

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

#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

template <typename T>
class Vector
{
public:
    T &operator[] (int i) const { return p[i]->get(); }
    template <typename D>
    void push_back(D &x) { p.push_back(ptr_t(new DerivedNode<D>(x))); }

private:
    class Node
    {
    public:
        virtual T &get() = 0;
    };

    template <typename D>
    class DerivedNode : public Node
    {
    public:
        DerivedNode(D &x) : x(x) {}
        virtual D &get() { return x; }
    private:
        D x;
    };

    typedef boost::shared_ptr<Node> ptr_t;
    std::vector<ptr_t> p;
};

///////////////////////////////////////

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

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

        int child_mem;
};


int main()
{
    Vector<Parent> v;

    v.push_back(Parent());
    v.push_back(Child());

    v[0].write();
    v[1].write();
}
2 голосов
/ 11 сентября 2010

Прежде всего, ваши требования все еще не совсем ясны.Я предполагаю, что вы хотите «встроенное хранилище» для контейнера;так, например, в «полиморфном» vector все элементы должны быть смежными в памяти (только с отступом, необходимым для правильного выравнивания).

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

Если список типов теперь известен заранее, или если вы не хотите такого рода накладные расходы, вам придется поддерживать отдельный индексуказателей (или смещений от начала резервного хранилища) на элементы, так что вы можете сделать O (1) доступ.Кроме того, учитывая проблемы с выравниванием, я не уверен, что вы могли бы сделать это в полностью переносимом C ++ 03, хотя вы определенно можете это сделать в C ++ 0x.

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