Pergunta

Como regra geral, eu prefiro usar de valor, em vez de ponteiro semântica em C++ (ou seja, usando vector<Class> em vez de vector<Class*>).Geralmente a ligeira perda de desempenho é mais do que fez para não ter que lembrar de excluir objetos alocados dinamicamente.

Infelizmente, valor coleções não funcionam quando você deseja armazenar uma variedade de tipos de objeto que todas derivam de uma base comum.Veja o exemplo abaixo.

#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

}

A minha pergunta é:Posso ter o meu bolo (valores semânticos) e comê-lo também (polimórfico recipientes)?Ou tenho que usar ponteiros?

Foi útil?

Solução

Uma vez que os objetos de classes diferentes terão diferentes tamanhos, você iria acabar com o corte problema se você armazená-los como valores.

Uma solução razoável é a de armazenar o recipiente seguro ponteiros inteligentes.Eu costumo usar o boost::shared_ptr que é seguro armazenar em um recipiente.Observe que, std::auto_ptr é não.

vector<shared_ptr<Parent>> vec;
vec.push_back(shared_ptr<Parent>(new Child()));

shared_ptr usa a contagem de referência de forma a não excluir subjacente instância até que todas as referências são removidos.

Outras dicas

Sim, você pode.

O impulso.ptr_container biblioteca fornece polimórficos valor semântico versões do padrão de contêineres.Você só tem que passar um ponteiro para um heap-objeto alocado, e o recipiente de tomar posse e todas as outras operações de fornecer um valor semântico , exceto para recuperar a propriedade, que lhe dá todas as vantagens de um valor semântico usando um ponteiro inteligente.

Eu só queria ressaltar que o vector<Foo> é geralmente mais eficiente do que o vetor de<Foo*>.Em um vetor<Foo>, todos os Foos vai ser adjacentes uns aos outros na memória.Assumindo um frio TLB e cache, a primeira leitura, a página para a TLB e puxe um pedaço do vetor para o L# armazena em cache;leituras subseqüentes vai usar a quente cache e carregado TLB, com eventuais erros de cache e menos freqüentes TLB falhas.

Contraste isso com um vetor<Foo*>:Como você preenche o vetor, obter Foo*'s do seu alocador de memória.Supondo que sua atribuição é, não extremamente inteligente, (tcmalloc?) ou você preencher o vetor lentamente ao longo do tempo, a localização de cada Foo é, provavelmente, muito além dos outros Foos:talvez apenas por centenas de bytes, talvez megabytes além.

No pior caso, como você analisar através de um vetor<Foo*> e a referência de cada ponteiro, será cobrada uma TLB e a falhas de cache miss -- isto vai acabar sendo um monte mais lento do que se você tivesse um vetor<Foo>.(Bem, no pior caso, cada Foo paginada fora do disco, e a cada leitura incorre em um disco seek() e read() para mover a página de volta para a RAM.)

Por isso, fique usando o vetor de<Foo> sempre que apropriado.:-)

A maioria dos tipos de recipiente deseja resumo o particular a estratégia de armazenamento, seja ele vinculado lista, vetor, baseada em árvore ou o que você tem.Por esta razão, você vai ter problemas com o tanto de possuir e de consumir o referido bolo (i.é., o bolo é mentira (NB:alguém tinha que fazer essa piada)).

Então, o que fazer?Bem, existem algumas opções bonitos, mas a maioria vai reduzir para variantes em um dos poucos temas ou combinações dos mesmos:escolher ou inventar um adequado ponteiro inteligente, jogando com modelos ou modelo modelos em alguma forma inteligente, usando uma interface comum para containees que fornece um gancho para a implementação de per-containee duplo-despacho.

Há básica da tensão entre os seus dois objetivos, portanto, você deve decidir o que você deseja e, em seguida, tente criar algo que você fica basicamente o que você deseja.Ele é é possível fazer algumas agradável e inesperado truques para obter ponteiros para olhar como valores inteligente o suficiente para a contagem de referência e inteligente o suficiente para implementações de uma fábrica.A idéia básica é usar a contagem de referência e de cópia-na-demanda e constness e (para factor) uma combinação de pré-processador, modelos, e C++'s de inicialização estático regras para obter algo que é tão inteligente quanto possível sobre como automatizar o ponteiro conversões.

Eu, no passado, passou algum tempo tentando imaginar como usar Proxy Virtual / Envelope-Letra / que bonito truque com referência contados ponteiros para realizar algo como uma base para o valor semântico de programação em C++.

E eu acho que isso poderia ser feito, mas você tem que prestar bastante fechado, C#-código gerenciado mundo de dentro do C++ (embora a partir do qual você poderia romper com C++ subjacente quando necessário).Então, eu tenho um monte de simpatia para a sua linha de pensamento.

Você também pode considerar boost::nenhum.Eu usei-o para recipientes heterogêneos.Ao ler o valor de volta, você precisa executar uma any_cast.Ele irá lançar um bad_any_cast em caso de falha.Se isso acontecer, você pode pegar e passar para o próximo tipo.

Eu acreditar ele irá lançar um bad_any_cast se você tentar any_cast uma classe derivada para sua base.Eu tentei:

  // But you sort of can do it with boost::any.

  vector<any> valueVec;

  valueVec.push_back(any(Parent()));
  valueVec.push_back(any(Child()));        // remains a Child, wrapped in an Any.

  Parent p = any_cast<Parent>(valueVec[0]);
  Child c = any_cast<Child>(valueVec[1]);
  p.write();
  c.write();

  // Output:
  //
  // Parent: 1
  // Child: 2, 2

  // Now try casting the child as a parent.
  try {
      Parent p2 = any_cast<Parent>(valueVec[1]);
      p2.write();
  }
  catch (const boost::bad_any_cast &e)
  {
      cout << e.what() << endl;
  }

  // Output:
  // boost::bad_any_cast: failed conversion using boost::any_cast

Tudo o que está sendo dito, também gostaria de ir para o shared_ptr rota primeiro!Apenas pensei que isso poderia ser de algum interesse.

Dê uma olhada no static_cast e reinterpret_cast
Em C++ Programming Language, 3rd ed, Bjarne Stroustrup descreve na página 130.Há uma seção inteira sobre isso no Capítulo 6.
Você pode reformular sua classe Pai para a Criança de classe.Isso requer que você saiba quando cada um é qual.No livro, o Dr.Stroustrup fala sobre diferentes técnicas para evitar esta situação.

Não faça isso.Isso anula o polimorfismo que você está tentando alcançar o primeiro lugar!

Só para acrescentar uma coisa a todos 1800 INFORMAÇÕES já disse.

Você pode querer dar uma olhada em "Mais Eficaz C++" por Scott Meyers ", Item 3:Nunca tratar matrizes polimorficamente" a fim de entender melhor esse problema.

Eu estou usando a minha própria coleção de modelo de classe com exposta tipo de valor semântico, mas internamente ele armazena ponteiros.Ele está usando uma custom iterador de classe que, quando cancelou a referência obtém uma referência de valor, em vez de um ponteiro.Copiar a coleção de profundidade item de cópias, em vez de duplicados ponteiros, e isso é onde a maioria sobrecarga de mentiras (realmente uma questão menor, considerado o que eu recebo em vez disso).

Essa é uma idéia que poderia atender às suas necessidades.

Enquanto procurava uma resposta para este problema, deparei-me com este e uma pergunta semelhante.As respostas a outra pergunta que você vai encontrar duas soluções sugeridas:

  1. Uso std::opcional ou boost::opcional e um visitante padrão.Esta solução torna difícil para adicionar novos tipos, mas de fácil adicionar novas funcionalidades.
  2. Usar uma classe de wrapper semelhante ao que Sean Pai apresenta em sua palestra.Esta solução torna-se difícil adicionar novas funcionalidades, mas fácil de adicionar novos tipos.

O wrapper define a interface que você precisa para suas aulas e contém um ponteiro para um objeto.A implementação da interface é feito com higiene funções.

Aqui está um exemplo de implementação deste padrão:

class Shape
{
public:
    template<typename T>
    Shape(T t)
        : container(std::make_shared<Model<T>>(std::move(t)))
    {}

    friend void draw(const Shape &shape)
    {
        shape.container->drawImpl();
    }
    // add more functions similar to draw() here if you wish
    // remember also to add a wrapper in the Concept and Model below

private:
    struct Concept
    {
        virtual ~Concept() = default;
        virtual void drawImpl() const = 0;
    };

    template<typename T>
    struct Model : public Concept
    {
        Model(T x) : m_data(move(x)) { }
        void drawImpl() const override
        {
            draw(m_data);
        }
        T m_data;
    };

    std::shared_ptr<const Concept> container;
};

Diferentes formas, em seguida, são implementados como estruturas regulares/classes.Você é livre para escolher se pretende utilizar funções de membro ou de função gratuita (mas você terá que atualizar a implementação acima para usar funções de membro).Eu prefiro grátis funções:

struct Circle
{
    const double radius = 4.0;
};

struct Rectangle
{
    const double width = 2.0;
    const double height = 3.0;
};

void draw(const Circle &circle)
{
    cout << "Drew circle with radius " << circle.radius << endl;
}

void draw(const Rectangle &rectangle)
{
    cout << "Drew rectangle with width " << rectangle.width << endl;
}

Agora você pode adicionar tanto Circle e Rectangle objectos para o mesmo std::vector<Shape>:

int main() {
    std::vector<Shape> shapes;
    shapes.emplace_back(Circle());
    shapes.emplace_back(Rectangle());
    for (const auto &shape : shapes) {
        draw(shape);
    }
    return 0;
}

A desvantagem deste modelo é que ele requer uma grande quantidade de clichê na interface, uma vez que cada função deve ser definido por três vezes.A vantagem é que você obter cópia-semântica:

int main() {
    Shape a = Circle();
    Shape b = Rectangle();
    b = a;
    draw(a);
    draw(b);
    return 0;
}

Isso produz:

Drew rectangle with width 2
Drew rectangle with width 2

Se você estiver preocupado com a shared_ptr, você pode substituí-lo com um unique_ptr.No entanto, ele não será mais copiáveis e você terá que mover todos os objetos ou implementar copiar manualmente.Sean Pai explica isso em detalhes em seu discurso e de uma implementação é mostrada na referida resposta.

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