Pergunta

Se eu quiser fazer uma adaptável classe, e torná-lo possível selecionar diferentes algoritmos de fora - o que é a melhor aplicação em C ++

Eu vejo principalmente duas possibilidades:

  • Use uma classe base abstrata e passar objeto concreto no
  • Use um modelo

Aqui está um pequeno exemplo, implementado nas várias versões:

Versão 1: classe base Abstract

class Brake {
public: virtual void stopCar() = 0;  
};

class BrakeWithABS : public Brake {
public: void stopCar() { ... }
};

class Car {
  Brake* _brake;
public:
  Car(Brake* brake) : _brake(brake) { brake->stopCar(); }
};

Versão 2a: modelo

template<class Brake>
class Car {
  Brake brake;
public:
  Car(){ brake.stopCar(); }
};

Versão 2b: Modelo e herança private

template<class Brake>
class Car : private Brake {
  using Brake::stopCar;
public:
  Car(){ stopCar(); }
};

Vindo de Java, estou naturalmente inclinados para usar sempre a versão 1, mas as versões modelos parecem ser preferido, muitas vezes, por exemplo, em código STL? Se isso é verdade, não é apenas por causa da eficiência da memória etc (sem herança, sem chamadas de funções virtuais)?

Eu percebo que não há uma grande diferença entre a versão 2A e 2B, veja C ++ FAQ .

Você pode comentar sobre essas possibilidades?

Foi útil?

Solução

Isso depende de seus objetivos. Você pode usar a versão 1, se você

  • A intenção de substituir os freios de um carro (em tempo de execução)
  • A intenção de passar de carro ao redor para funções não-modelo

Eu geralmente preferem a versão 1 utilizando o polimorfismo em tempo de execução, porque ainda é flexível e permite que você tenha o carro ainda têm o mesmo tipo: Car<Opel> é um outro tipo de Car<Nissan>. Se seus objetivos são um excelente desempenho ao usar os freios com freqüência, eu recomendo que você use a abordagem templated. By the way, isso é chamado de formulação de políticas baseadas. Você fornece um política de freio . Exemplo porque você disse que programado em Java, possivelmente você ainda não são muito experiente com C ++. Uma maneira de fazê-lo:

template<typename Accelerator, typename Brakes>
class Car {
    Accelerator accelerator;
    Brakes brakes;

public:
    void brake() {
        brakes.brake();
    }
}

Se você tem muitas políticas que você pode agrupá-los em sua própria estrutura, e passar esse, por exemplo, como um SpeedConfiguration coleta Accelerator, Brakes e alguns mais. Em meus projetos tento manter uma boa quantidade de código do modelo-livre, permitindo que eles sejam compilados uma vez em seus próprios arquivos de objetos, sem precisar seu código nos cabeçalhos, mas ainda permitindo que o polimorfismo (via funções virtuais). Por exemplo, você pode querer manter dados e funções que o código não-modelo provavelmente vai chamar em muitas ocasiões em uma classe de base comuns:

class VehicleBase {
protected:
    std::string model;
    std::string manufacturer;
    // ...

public:
    ~VehicleBase() { }
    virtual bool checkHealth() = 0;
};


template<typename Accelerator, typename Breaks>
class Car : public VehicleBase {
    Accelerator accelerator;
    Breaks breaks;
    // ...

    virtual bool checkHealth() { ... }
};

A propósito, que também é a abordagem que C ++ córregos uso: std::ios_base contém bandeiras e outras coisas que não dependem do tipo char ou traços como openmode, bandeiras formato e outras coisas, enquanto std::basic_ios, então, é um modelo de classe que herda-lo. Isso também reduz o inchaço de código compartilhando o código que é comum a todas as instâncias de um modelo de classe.

herança private?

herança Privada deve ser evitado em geral. Só muito raramente útil e contenção é uma idéia melhor na maioria dos casos. caso comum onde o oposto é verdadeiro quando o tamanho é realmente crucial (política baseada classe string, por exemplo):. vazio classe base Optimization pode aplicar ao derivar de uma classe política de vazio (apenas contêm funções)

Leia Usos e abusos da Herança por Herb Sutter.

Outras dicas

A regra de ouro é:

1) Se a escolha do tipo de concreto é feita em tempo de compilação, preferem um modelo. Será mais seguro (erros de tempo de compilação vs erros tempo de execução) e, provavelmente, melhor otimizado. 2) Se a escolha é feita em tempo de execução (ou seja, como um resultado da ação de um usuário) não há realmente nenhuma escolha -. Herança uso e virtuais funções

Outras opções:

  1. Use o Visitor Pattern (deixar o trabalho de código externo na sua classe).
  2. externalização alguma parte de sua classe, por exemplo através de iteradores, que o código-base iterador genérico pode trabalhar neles. Isso funciona melhor se o seu objeto é um contêiner de outros objetos.
  3. Veja também a Estratégia Padrão (há c exemplos ++ dentro)

Os modelos são uma maneira de deixar uma classe usar uma variável do que você realmente não se preocupam com o tipo. A herança é uma maneira de definir o que uma classe é baseado em seus atributos. É a "é-um" versus "tem-um" questão.

A maioria da sua pergunta já foi respondida, mas eu quis elaborar sobre este bit:

Vindo de Java, eu sou naturalmente inclinado a usar sempre a versão 1, mas as versões modelos parecem ser preferida muitas vezes, por exemplo em código STL? E se isso é verdade, não é apenas por causa de a eficiência da memória etc (sem herança, sem chamadas de funções virtuais)?

Isso é parte dela. Mas outro fator é o tipo de segurança adicional. Quando você tratar uma BrakeWithABS como um Brake, você perde informações de tipo. Você não sabe que o objeto é realmente uma BrakeWithABS. Se é um parâmetro de modelo, você tem o tipo exato disponível, o que em alguns casos pode permitir que o compilador para um melhor desempenho typechecking. Ou pode ser útil para garantir que a sobrecarga correta de uma função é chamada. (Se stopCar() passa o objeto Brake para uma segunda função, que pode ter uma sobrecarga separada para BrakeWithABS, que não será chamado se você tivesse herança usado, e seu BrakeWithABS tinha sido fundido a um Brake.

Outro fator é que ele permite mais flexibilidade. Por que todas as implementações de freio tem que herda da mesma classe base? Será que a classe base realmente tem nada para trazer para a mesa? Se eu escrever uma classe que expõe as funções de membro esperados, não é bom o suficiente para agir como um freio? Muitas vezes, usando explicitamente interfaces ou classes base abstratas restringir seu código mais do que o necessário.

(modelos Note, eu não estou dizendo que deve ser sempre a solução preferida. Existem outras preocupações que possam afectar esta, que vão desde a velocidade de compilação para "o que os programadores na minha equipe estão familiarizados com" ou simplesmente "o que eu prefiro" . E, às vezes, você necessidade polimorfismo em tempo de execução, caso em que a solução de modelo simplesmente não é possível)

esta resposta é mais ou menos correta. Quando você quer algo parametrizada em tempo de compilação - você deve preferir modelos. Quando você quer algo parametrizada em tempo de execução, você deve preferir funções virtuais que está sendo substituído.

No entanto , usando modelos não impede você de fazer ambos (fazendo a versão do modelo mais flexível):

struct Brake {
    virtual void stopCar() = 0;
};

struct BrakeChooser {
    BrakeChooser(Brake *brake) : brake(brake) {}
    void stopCar() { brake->stopCar(); }

    Brake *brake;
};

template<class Brake>
struct Car
{
    Car(Brake brake = Brake()) : brake(brake) {}
    void slamTheBrakePedal() { brake.stopCar(); }

    Brake brake;
};


// instantiation
Car<BrakeChooser> car(BrakeChooser(new AntiLockBrakes()));

Dito isto, eu provavelmente não usar modelos para isso ... Mas seu sabor realmente apenas pessoal.

classe base Abstract tem sobre sobrecarga de chamadas virtuais, mas tem uma vantagem que todas as classes derivadas são classes realmente de base. Não é assim quando você usa modelos -. Viaturas e carro não estão relacionados uns com os outros e você vai ter que quer dynamic_cast e verifique se nulo ou ter modelos para todo o código que lida com carro

Use interface de se supor para suportar diferentes classes pausa e sua hierarquia ao mesmo tempo.

Car( new Brake() )
Car( new BrakeABC() )
Car( new CoolBrake() )

E você não sabe esta informação em tempo de compilação.

Se você sabe que quebrar você está indo para uso 2b é escolha certa para você especificar diferentes classes de viaturas. Brake, neste caso, será o seu carro "Estratégia" e você pode definir um padrão.

Eu não usaria 2a. Em vez disso você pode adicionar métodos estáticos para quebrar e chamá-los sem exemplo.

Pessoalmente eu gostaria de hair preferem usar Interfaces sobre modelos por causa de vários motivos:

  1. Modelos Compilando e erros de ligação são, por vezes enigmática
  2. É difícil depurar um código que baseado em modelos (pelo menos no IDE Visual Studio)
  3. Os modelos podem fazer seus binários maior.
  4. Modelos exigir que você coloque toda a sua código no cabeçalho do arquivo, que faz com que a classe de modelo um pouco mais difícil de entender.
  5. Os modelos são difíceis de manter por programadores iniciantes.

Eu uso Apenas modelos quando as mesas virtuais criar algum tipo de sobrecarga.

Claro, esta é apenas a minha auto opinião.

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