Насколько плохо использовать указатель void в объявлении std :: vector? - PullRequest
0 голосов
/ 21 октября 2010

У меня есть два разных класса, как показано ниже:

class text
{ };

class element
{ };

И я хочу хранить их в class node:

template <typename T>
class node
{
    T cargo;

    std::vector<void*> children;

    node(T cargo) : cargo(cargo)
    { };

    void add_child(T node)
    {
        this->children.push_back((void*) node);
    }
}

Таким образом, я бы назвал узел таким образом, храня и text, и element s:

element div;
text msg;

node<element> wrapper(div);

wrapper.add_child(msg);

РЕДАКТИРОВАТЬ : Чтобы вернуть содержимое, я использую T typedef type; и преобразую пустой указатель в (type*).

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

Заранее спасибо!

Ответы [ 6 ]

6 голосов
/ 21 октября 2010
#include <vector>
using namespace std;

class Element {};
class Text {};
class Nothing {};

class Node
{
private:
    vector< Node* >     children_;
protected:
    Node() {}
public:
    void add( Node* p ) { children_.push_back( p ); }
    virtual ~Node() {}
};

template< class Cargo >
class CargoNode
    : public Node
{
private:
    Cargo   cargo_;
public:
    CargoNode(): cargo_() {}
};

typedef CargoNode< Element >    ElementNode;
typedef CargoNode< Text >       TextNode;
typedef CargoNode< Nothing >    RootNode;

int main()
{
    RootNode*   root    = new RootNode;

    root->add( new ElementNode );
    root->add( new ElementNode );
    root->add( new TextNode );
    root->add( new ElementNode );   
    // Etc.
}

Приветствия & hth.,

PS: проверка ошибок, управление сроком службы, итерация и т. Д. В этом примере кода опущены.

5 голосов
/ 21 октября 2010

Я бы сказал, что void * почти всегда "плох" (для некоторого определения плохого). Конечно, есть лучшие способы выразить то, что вы пытаетесь сделать. Если бы я писал этот код, и я знал типы значений, которые я собирался вставить, то я бы рассмотрел использование Boost.Variant . Если бы я этого не сделал (например, это было предоставлено как библиотека кому-то другому для «заполнения»), я бы использовал Boost.Any

Например:

template <class T, class U>
struct node
{
    typedef boost::variant<T, U> child_type;

    std::vector<child_type> children;

    void add_child(T const &t)
    {
        children.push_back(t);
    }

    void add_child(U const &u)
    {
        children.push_back(u);
    }
};

...

node<text, element> n;
n.add_child(text("foo"));

Типовое объединение без повышения:

struct node
{
    struct child
    {
        int type; // 0 = text; 1 = element

        union 
        {
            text*    t;
            element* e;
        } u;
    };

    std::vector<child> children;

    void add_child(text* t)
    {
        child ch;
        ch.type = 0;
        ch.u.t  = t;

        children.push_back(ch);
    }

    void add_child(element* e)
    {
        child ch;
        ch.type = 1;
        ch.u.e  = t;

        children.push_back(ch);
    }
};

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

1 голос
/ 21 октября 2010

Если значение вашего контейнера ограничено небольшим количеством типов, вы можете достичь этого, используя boost::variant, как показано здесь:

#include <vector>
#include <boost/variant.hpp>

using namespace std;

class text
{ };

class element
{ };

template <typename T>
class node
{
    T cargo;

    static std::vector<boost::variant<text, element>> children;

    node(const T& cargo) : cargo(cargo)
    { };

    void add_child(const T& node)
    {
        children.push_back(boost::variant<text, element>(node));
    }
};

Я позволил себе предложить пару других модов - используйте ссылку const вместо передачи по значению в конструкторе node и add_child; сделайте контейнер children статическим, так как я не думаю, что для каждого node<T> имеет смысл иметь свой собственный контейнер. В этом случае для многопоточного использования add_child потребуется блокировка. Эти комментарии применяются независимо от того, можете ли вы использовать Boost или нет в своем окончательном решении.

Вы можете выполнять операции с элементами vector, используя get или static_visitor - последний вариант предпочтительнее, поскольку вы можете сделать это универсальным - как показано здесь Пример итерации vector, аналогичный тому, который вы использовали бы для этого решения:

class times_two_generic
    : public boost::static_visitor<>
{
public:

    template <typename T>
    void operator()( T & operand ) const
    {
        operand += operand;
        cout << operand << endl;
    }

};

std::vector< boost::variant<int, std::string> > vec;
vec.push_back( 21 );
vec.push_back( "hello " );

times_two_generic visitor;
std::for_each(
      vec.begin(), vec.end()
   , boost::apply_visitor(visitor)
   );

Вывод:

42

привет привет

1 голос
/ 21 октября 2010

Определите общий базовый класс для element и text, и тогда add_child может взять указатель на базовый класс, а вектор может хранить указатели на базовый класс.

1 голос
/ 21 октября 2010

Как бы вы вернули их, если бы сделали это? Из void* невозможно определить, что на самом деле хранится по адресу.

Edit: Если вы всегда выполняете приведение к T*, вы можете просто взять T* в качестве параметра.

0 голосов
/ 21 октября 2010

Во-первых, нет такой вещи, как "плохо", чтобы использовать пустой указатель. Забудьте все соглашения и бла-бла и сделайте то, что наиболее подходит для вашего случая.

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

...