Хранение списка произвольных объектов в C++

вы можете сделать что-то вроде этого:

describe 'validations' do
  let(:some_variable_object) { SomeVariable.new } 
  let(:new_foo) { described_class.new(some_variable: some_variable_object) }

  context 'when some_variable is valid' do
    before do
      allow(some_variable_object).to receive(:valid?) { true }
    end

    it 'is valid' do
     expect(new_foo).to be_valid
    end

    it 'does not have errors related to some_variable' do
      expect(new_foo.errors[:some_variables]).to be_empty
    end
  end

тогда вы можете сделать то же самое, чтобы проверить обратное, когда some_variable недействительно ... сейчас есть инструменты, которые помогут вам настроить объекты в спецификации легко (FactoryBot).

7
задан Ferruccio 3 March 2009 в 19:11
поделиться

13 ответов

Ваш пример с помощью Повышения. Вариант и посетитель:

#include <string>
#include <list>
#include <boost/variant.hpp>
#include <boost/foreach.hpp>

using namespace std;
using namespace boost;

typedef variant<string, int, bool> object;

struct vis : public static_visitor<>
{
    void operator() (string s) const { /* do string stuff */ }
    void operator() (int i) const { /* do int stuff */ }
    void operator() (bool b) const { /* do bool stuff */ }      
};

int main() 
{
    list<object> List;

    List.push_back("Hello World!");
    List.push_back(7);
    List.push_back(true);

    BOOST_FOREACH (object& o, List) {
        apply_visitor(vis(), o);
    }

    return 0;
}

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

14
ответ дан 6 December 2019 в 04:45
поделиться

C++ не поддерживает неоднородные контейнеры.

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

class Dummy {
   virtual void whoami() = 0;
};

class Lizard : public Dummy {
   virtual void whoami() { std::cout << "I'm a lizard!\n"; }
};


class Transporter : public Dummy {
   virtual void whoami() { std::cout << "I'm Jason Statham!\n"; }
};

int main() {
   std::list<Dummy*> hateList;
   hateList.insert(new Transporter());
   hateList.insert(new Lizard());

   std::for_each(hateList.begin(), hateList.end(), 
                 std::mem_fun(&Dummy::whoami));
   // yes, I'm leaking memory, but that's besides the point
}

Если Вы собираетесь использовать boost можно попробовать boost::any. Вот пример использования boost::any.

Можно найти эту превосходную статью двух ведущих экспертов C++ по интересу.

Теперь, boost::variant другая вещь состоит в том, чтобы высматривать как j_random_hacker упомянутый. Так, вот сравнение для получения справедливой идеи того, что использовать.

С a boost::variant код выше выглядел бы примерно так:

class Lizard {
   void whoami() { std::cout << "I'm a lizard!\n"; }
};

class Transporter {
   void whoami() { std::cout << "I'm Jason Statham!\n"; }
};

int main() {

   std::vector< boost::variant<Lizard, Transporter> > hateList;

   hateList.push_back(Lizard());
   hateList.push_back(Transporter());

   std::for_each(hateList.begin(), hateList.end(), std::mem_fun(&Dummy::whoami));
}
13
ответ дан 6 December 2019 в 04:45
поделиться

boost::variant подобно предложению dirkgently boost::any, но поддерживает Шаблон "посетитель", означая, что легче добавить определенный для типа код позже. Кроме того, это выделяет значения на стеке вместо того, чтобы использовать динамическое выделение, ведя к немного более эффективному коду.

Править: Поскольку litb указывает в комментариях, с помощью variant вместо any средства можно только держать значения из одного из предуказанного списка типов. Это часто - сила, хотя это могла бы быть слабость в случае автора вопроса.

Вот пример (не использующий Шаблон "посетитель" хотя):

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

using namespace std;
using namespace boost;

...

vector<variant<int, string, bool> > v;

for (int i = 0; i < v.size(); ++i) {
    if (int* pi = get<int>(v[i])) {
        // Do stuff with *pi
    } else if (string* si = get<string>(v[i])) {
        // Do stuff with *si
    } else if (bool* bi = get<bool>(v[i])) {
        // Do stuff with *bi
    }
}

(И да, необходимо технически использовать vector<T>::size_type вместо int для iвведите, и необходимо технически использовать vector<T>::iterator вместо этого так или иначе, но я пытаюсь сохранить это простым.)

20
ответ дан 6 December 2019 в 04:45
поделиться

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

C++ имеет более тяжелый акцент на безопасность типов, чем Java, и это очень небезопасно типом.

Тем не менее, если объекты не имеют ничего общего, почему Вы храните их вместе?

Если у них действительно есть общие черты, можно сделать класс для них для наследования; поочередно, используйте повышение::любой. если они наследовали, имеют виртуальные функции, чтобы назвать, или использовать dynamic_cast <>, если Вы действительно имеете к.

12
ответ дан 6 December 2019 в 04:45
поделиться

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

class MyData
{
public:
  // base classes of polymorphic types should have a virtual destructor
  virtual ~MyData() {} 

  // hand off to protected implementation in derived classes
  void DoSomething() { this->OnDoSomething(); } 

protected:
  // abstract, force implementation in derived classes
  virtual void OnDoSomething() = 0;
};

class MyIntData : public MyData
{
protected:
  // do something to int data
  virtual void OnDoSomething() { ... } 
private:
  int data;
};

class MyComplexData : public MyData
{
protected:
  // do something to Complex data
  virtual void OnDoSomething() { ... }
private:
  Complex data;
};

void main()
{
  // alloc data objects
  MyData* myData[ 2 ] =
  {
    new MyIntData()
  , new MyComplexData()
  };

  // process data objects
  for ( int i = 0; i < 2; ++i ) // for each data object
  {
     myData[ i ]->DoSomething(); // no type cast needed
  }

  // delete data objects
  delete myData[0];
  delete myData[1];
};
3
ответ дан 6 December 2019 в 04:45
поделиться

Печально нет никакого простого способа сделать это в C++. Необходимо создать базовый класс сами и получить все другие классы из этого класса. Создайте вектор указателей базового класса и затем используйте dynamic_cast (который идет с его собственным временем выполнения наверху) найти фактический тип.

2
ответ дан 6 December 2019 в 04:45
поделиться

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

#include <iostream>
#include <vector>

using namespace std;

int main() {

  int a = 4;
  string str = "hello";

  vector<void*> list;
  list.push_back( (void*) &a );
  list.push_back( (void*) &str );

  cout <<  * (int*) list[0] << "\t" << * (string*) list[1] << endl;

  return 0;
}
2
ответ дан 6 December 2019 в 04:45
поделиться

Я - довольно неопытное, но здесь - то, что я пошел бы с -

  1. Создайте базовый класс для всех классов, которыми необходимо управлять.
  2. Запишите контейнерный класс / контейнерный класс повторного использования. (Пересмотренный после наблюдения других ответов - Моя предыдущая точка была слишком загадочной.)
  3. Напишите подобный код.

Я уверен, что намного лучшее решение возможно. Я также уверен, что лучшее объяснение возможно. Я узнал, что у меня есть некоторые плохие привычки программирования на C++, таким образом, я попытался передать свою идею, не входя в код.

Я надеюсь, что это помогает.

1
ответ дан 6 December 2019 в 04:45
поделиться

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

Давайте отклоним Ваш пример и рассмотрим что-то ближе к реальному примеру. А именно, некоторый код я видел в реальном проекте с открытым исходным кодом. Это попыталось эмулировать CPU в символьном массиве. Следовательно это поместило бы в массив однобайтовый "код операции", сопровождаемый 0, 1 или 2 байта, которые могли быть символом, целым числом или указателем на строку, на основе кода операции. Для обработки этого это включило большую разрядную игру.

Мое простое решение: 4 отдельных стека <> s: Один для перечисления "кода операции" и один каждый для символов, ints и строки. Возьмите следующее от стопки кода операции, и берет Вас который из других трех для получения операнда.

Существует очень хороший шанс, Ваша фактическая проблема может быть решена похожим способом.

1
ответ дан 6 December 2019 в 04:45
поделиться

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

Посмотрите объектные обертки для встроенных типов от структур данных и алгоритмов с объектно-ориентированными шаблонами разработки в C++.

С перенесенным объектом можно использовать идентификатор типа C++ () оператор для сравнения типа. Я вполне уверен, следующее сравнение будет работать: if (typeid(o) == typeid(Int)) [где Интервал был бы перенесенным классом для международного типа примитива, и т.д....] (иначе просто добавляют функцию к Вашим примитивным оберткам, которая возвращает идентификатор типа и таким образом: if (o.get_typeid() == typeid(Int)) ...

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

2
ответ дан 6 December 2019 в 04:45
поделиться

RTTI (Информация о типе выполнения) в C++ всегда был жесток, особенно кросс-компилятор.

Вы - наилучший вариант, должен использовать STL и определить интерфейс для определения типа объекта:

public class IThing
{
   virtual bool isA(const char* typeName);
}

void myFunc()
{
   std::vector<IThing> things;

   // ...

   things.add(new FrogThing());
   things.add(new LizardThing());

   // ...

   for (int i = 0; i < things.length(); i++)
   {
       IThing* pThing = things[i];

       if (pThing->isA("lizard"))
       {
         // do this
       }
       // etc
   }
}

Mike

0
ответ дан 6 December 2019 в 04:45
поделиться

Короткий ответ..., Вы не можете.

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

0
ответ дан 6 December 2019 в 04:45
поделиться

Ну, Вы могли создать базовый класс и затем создать классы, которые наследовались ему. Затем сохраните их в станд.:: вектор.

0
ответ дан 6 December 2019 в 04:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: