Pergunta

Em Java, você pode ter uma lista de objetos. Você pode adicionar objetos de vários tipos, em seguida, recuperá-los, verificar seu tipo, e executar a ação apropriada para esse tipo.
Por exemplo: (desculpas se o código não é exatamente correto, eu vou de memória)

List<Object> list = new LinkedList<Object>();

list.add("Hello World!");
list.add(7);
list.add(true);

for (object o : list)
{
    if (o instanceof int)
        ; // Do stuff if it's an int
    else if (o instanceof String)
        ; // Do stuff if it's a string
    else if (o instanceof boolean)
        ; // Do stuff if it's a boolean
}

Qual é a melhor maneira de replicar esse comportamento em C ++?

Foi útil?

Solução

O seu exemplo usando Boost.Variant e um visitante:

#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;
}

Uma coisa boa sobre o uso desta técnica é que se, mais tarde, você adicionar outro tipo de variante e você se esqueça de modificar um visitante para incluir esse tipo, ele não vai compilar. Você Have para apoiar todos os casos possíveis. Considerando que, se você usar um switch ou em cascata se declarações, é fácil esquecer de fazer a mudança em todos os lugares e introduzir um bug.

Outras dicas

boost::variant é semelhante à sugestão do de dirkgently boost::any, mas suporta o padrão Visitor, o que significa que é mais fácil de adicionar o código específico do tipo mais tarde. Além disso, ele aloca valores na pilha ao invés de usar a alocação dinâmica, levando ao código ligeiramente mais eficiente.

EDIT: Como litb pontos nos comentários, usando variant em vez de meios any você só pode armazenar valores de um de uma lista pré-definida de tipos. Isso é muitas vezes uma força, embora possa ser uma fraqueza no caso do autor da questão.

Aqui está um exemplo (não usando o padrão Visitor embora):

#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
    }
}

(E sim, você deve tecnicamente usar vector<T>::size_type vez de int para o tipo de i, e você deve tecnicamente usar vector<T>::iterator em vez de qualquer maneira, mas eu estou tentando mantê-lo simples.)

C ++ não suporta recipientes heterogéneas.

Se você não está indo para uso boost o hack é criar uma classe fictícia e tem todas as diferentes classes derivar dessa classe manequim. Criar um recipiente de sua escolha para objetos de classe espera manequim e você está pronto para ir.

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
}

Se você estiver indo para usar boost você pode tentar boost::any . Aqui é um exemplo do uso boost::any.

Você pode encontrar este excelente artigo por dois principais especialistas C ++ de interesse.

Agora, boost::variant é outra coisa de olhar para fora como j_random_hacker mencionado. Então, aqui está uma comparação para obter uma boa idéia do que usar.

Com um boost::variant o código acima seria algo parecido com isto:

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));
}

Como muitas vezes é esse tipo de coisa realmente útil? Estive programação em C ++ para muito poucos anos, em projetos diferentes, e nunca realmente queria um recipiente heterogêneo. Pode ser comum em Java, por alguma razão (eu tenho muito menos experiência em Java), mas para qualquer uso dele em um projeto Java que pode haver uma maneira de fazer algo diferente que irá funcionar melhor em C ++.

C ++ tem uma maior ênfase na segurança de tipos de Java, e isso é muito digite-inseguro.

Dito isto, se os objetos não têm nada em comum, por que você está armazenando-os juntos?

Se eles têm coisas em comum, você pode fazer uma classe para eles para herdar; Como alternativa, use boost :: qualquer. Se eles herdam, têm funções virtuais a chamada, ou o uso dynamic_cast <> se você realmente precisa.

Eu apenas gostaria de salientar que o uso de fundição tipo dinâmico de modo a filial com base em tipo geralmente aponta para falhas na arquitetura. Na maioria das vezes você pode conseguir o mesmo efeito usando funções virtuais:

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];
};

Infelizmente não há nenhuma maneira fácil de fazer isso em C ++. Você tem que criar uma classe base si mesmo e derivam todas as outras classes dessa classe. Criar um vetor de ponteiros de classe base e, em seguida, uso dynamic_cast (que vem com sua própria sobrecarga de tempo de execução) para encontrar o tipo real.

Apenas para a completude deste tópico, gostaria de mencionar que você pode realmente fazer isso com puro C usando void * e, em seguida, convertê-lo em tudo o que tem que ser (ok, o meu exemplo não é puro C, uma vez que usa vetores mas isso me poupa algum código). Isto irá funcionar se você sabe o tipo seus objetos são, ou se você armazenar um lugar campo que lembra disso. Você certamente não quer fazer isso, mas aqui é um exemplo para mostrar que é possível:

#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;
}

Enquanto você não pode armazenar tipos primitivos em recipientes, você pode criar tipo primitivo invólucro de classes que será semelhante a tipos primitivos autoboxed do Java (no seu exemplo os literais digitadas primitivos estão realmente sendo autoboxed); instâncias de que aparecem em código C ++ (e pode (quase) ser usado) como variáveis ??/ membros de dados primitivos.

Consulte envoltórios objeto para a Tipos Built-In de Estruturas de dados e Algoritmos com Object-Oriented design Patterns em C ++ .

Com o objeto embrulhado você pode usar o operador c ++ typeid () para comparar o tipo. Tenho a certeza a seguinte comparação vai funcionar: if (typeid(o) == typeid(Int)) [Int onde seria a classe enrolada para o tipo primitivo int, etc ...] (Caso contrário, basta adicionar uma função para seus wrappers primitivos que retorna um typeid e, assim: if (o.get_typeid() == typeid(Int)) ...

Dito isto, no que diz respeito ao seu exemplo, isso tem cheiro de código para mim. A menos que este é o único lugar onde você está verificando o tipo do objeto, Eu estaria inclinado a usar polimorfismo (especialmente se você tiver outros métodos / funções específicas em relação ao tipo). Neste caso, eu iria usar os wrappers primitivos adicionando uma classe com interface declarando o método diferido (para fazer 'fazer coisas'), que seria implementado por cada uma das suas classes primitivas embrulhadas. Com isso, você seria capaz de usar seu iterador recipiente e eliminar a sua declaração se (novamente, se você só tem esta comparação do tipo, configurando o método diferido usando polimorfismo apenas para este seria um exagero).

Eu sou um bastante inexperiente, mas aqui está o que eu iria com -

  1. Criar uma classe base para todas as classes que você precisa para manipular.
  2. classe contêiner
  3. classe contêiner Write / reutilização. (Revista depois de ver outras respostas -Meu ponto anterior era muito enigmática.)
  4. código semelhante Write.

Estou certo de que uma solução muito melhor é possível. Tenho também a certeza uma explicação melhor é possível. Eu aprendi que eu tenho algumas más C ++ hábitos de programação, então eu tentei transmitir minha idéia, sem entrar em código.

Espero que isso ajude.

Além do fato, como a maioria tem a pontas, você não pode fazer isso, ou mais importante, mais do que provável, você realmente não quer.

Vamos de dispensar seu exemplo, e considerar algo mais próximo de um exemplo da vida real. Especificamente, algum código que eu vi em um projeto open-source real. Ele tentou emular uma CPU em um array de caracteres. Por isso, seria colocado na matriz de um byte de um "código op", seguido por 0, 1 ou 2 bytes, que pode ser um caracter, um número inteiro, ou um ponteiro para uma cadeia de caracteres, com base no código op. Para lidar com isso, que envolveu um monte de bit-mexer.

A minha solução simples: 4 pilhas separadas <> s: Um para a enumeração "opcode" e um para cada carboniza, ints e string. Dê o próximo off a pilha de código de operação, eo levaria qual dos outros três para obter o operando.

Há uma chance muito boa de seu problema real pode ser tratado de uma forma similar.

Bem, você poderia criar uma classe base e, em seguida, criar classes que herdam dele. Em seguida, armazená-los em um std :: vector.

A resposta curta é ... você não pode.

A resposta longa é ... você teria que definir o seu próprio nova hierarquia de objetos que todos herdam de um objeto base. Em Java todos os objetos em última análise, descendem de "Objeto", que é o que permite que você faça isso.

RTTI (Run informações Tipo de tempo) em C ++ tem sido sempre difícil, especialmente cross-compilador.

Você é a melhor opção é usar STL e definir uma interface para determinar o tipo de objeto:

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top