Вопрос

Я пытаюсь научить себя C ++ и одному из традиционных " new language " Упражнения, которые я всегда использовал, - реализовать некоторую структуру данных, например, двоичное дерево или связанный список. В Java это было относительно просто: я мог определить некоторый класс Node, который поддерживал переменную экземпляра Object data, чтобы кто-то мог хранить объекты любого типа в каждом узле списка или дерева. (Позже я работал над изменением этого с помощью обобщений; вопрос не в этом.)

Я не могу найти похожий идиоматический способ хранения в C ++ & объекта любого типа. " В C я бы использовал указатель void; очевидно, что то же самое работает для C ++, но затем я сталкиваюсь с проблемами, когда создаю экземпляр std::string и пытаюсь сохранить его в списке / дереве (что-то о недопустимом приведении от std::string& к void*). Есть ли такой способ? Есть ли в C ++ эквивалент Java-объекта (или NSObject объекта Objective-C)?

Бонусный вопрос: если это не так, и мне нужно продолжать использовать пустые указатели, в чем смысл " right " способ сохранить static_cast<char*>(str.c_str()) в <=>? Я наткнулся на <=>, но это кажется довольно многословным для того, что я пытаюсь сделать. Есть ли лучший способ?

Это было полезно?

Решение

C ++ не имеет базового объекта, от которого наследуются все объекты, в отличие от Java. Обычный подход к тому, что вы хотите сделать, - это использовать шаблоны . Все контейнеры в стандартной библиотеке C ++ используют этот подход.

В отличие от Java, C ++ не использует полиморфизм / наследование для реализации универсальных контейнеров. В Java все объекты наследуются от Object, и поэтому любой класс может быть вставлен в контейнер, который принимает MyContainer. Шаблоны C ++, однако, являются конструкциями времени компиляции, которые инструктируют компилятор фактически генерировать разные классы для каждого типа, который вы используете. Так, например, если у вас есть:

template <typename T>
class MyContainer { ... };

Затем вы можете создать std::string, который принимает int объекты, и еще один MyContainer, который принимает <=> s.

MyContainer<std::string> stringContainer;
stringContainer.insert("Blah");

MyContainer<int> intContainer;
intContainer.insert(3342);

Другие советы

Вы можете взглянуть на boost :: any class. Это безопасный тип, вы можете поместить его в стандартные коллекции и вам не нужно связываться с какой-либо библиотекой, класс реализован в заголовочном файле.

Это позволяет вам писать код так:

#include <list>
#include <boost/any.hpp>

typedef std::list<boost::any> collection_type;

void foo()
{
    collection_type coll;
    coll.push_back(boost::any(10));
    coll.push_back(boost::any("test"));
    coll.push_back(boost::any(1.1));
}

Полная документация находится здесь: http: //www.boost .org / DOC / LIBS / 1_40_0 / DOC / HTML / any.html

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

Шаблоны - это статический способ сделать это. Они ведут себя как дженерики Java и C #, но на 100% статичны (время компиляции). Если вам не нужно хранить различные типы объектов в одном контейнере, используйте это (другие ответы описывают это очень хорошо).

Однако, если вам нужно хранить разные типы объектов в одном и том же контейнере, вы можете сделать это динамически, сохраняя указатели на базовом классе . Конечно, вы должны определить свою собственную иерархию объектов, поскольку такого & Quot; Object & Quot; класс в C ++:

#include <list>

class Animal {
public:
   virtual ~Animal() {}
};

class Dog : public Animal {
public:
   virtual ~Dog() {}
};

class Cat : public Animal {
public:
   virtual ~Cat() {}
};

int main() {
    std::list<Animal*> l;
    l.push_back(new Dog);
    l.push_back(new Cat);

    for (std::list<Animal*>::iterator i = l.begin(); i!= l.end(); ++i)
        delete *i;

    l.clear();

    return 0;
}

Умный указатель проще в использовании. Пример с boost::smart_ptr:

std::list< boost::smart_ptr<Animal> > List;
List.push_back(boost::smart_ptr<Animal>(new Dog));
List.push_back(boost::smart_ptr<Animal>(new Cat));
List.clear(); // automatically call delete on each stored pointer

Вы должны быть в состоянии превратить void* в string*, используя стандартные приведения в стиле C. Помните, что ссылка не обрабатывается как указатель при использовании, она обрабатывается как обычный объект. Таким образом, если вы передаете значение по ссылке на функцию, вам все равно придется разыменовать его, чтобы получить его адрес.

Однако, как уже говорили другие, лучший способ сделать это с помощью шаблонов

static_cast<char*>(str.c_str())

выглядит странно для меня. str.c_str() возвращает C-подобную строку, но с типом const char *, и для преобразования в char * вы обычно используете const_cast<char *>(str.c_str()). За исключением того, что это нехорошо, так как вы будете вмешиваться во внутренние органы string. Вы уверены, что не получили предупреждение об этом?

Вы должны быть в состоянии использовать static_cast<void *>(&str). Полученное вами сообщение об ошибке подсказывает мне, что вы ошиблись, поэтому, если вы можете опубликовать код, мы можем посмотреть на него. (Тип данных std::string& - это ссылка на <=>, а не указатель на него, поэтому сообщение об ошибке является правильным. Что я не знаю, так это то, как вы получили ссылку вместо указателя.)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top