Ok, isso é realmente difícil de confessar, mas eu tenho uma forte tentação no momento de herdar std::vector
.
Preciso de cerca de 10 algoritmos personalizados para vetor e quero que eles sejam diretamente membros do vetor. Mas, naturalmente, também quero ter o restante da std::vector
interface. Bem, minha primeira idéia, como cidadão cumpridor da lei, era ter um std::vector
membro da MyVector
classe. Mas então eu teria que reprovar manualmente toda a interface do std :: vector. Demais para digitar. Em seguida, pensei em herança privada, para que, em vez de reprovar métodos, eu escrevesse um monte de using std::vector::member
's na seção pública. Isso é tedioso também, na verdade.
E aqui estou eu, realmente acho que posso simplesmente herdar publicamente std::vector
, mas forneço um aviso na documentação de que essa classe não deve ser usada polimorficamente. Eu acho que a maioria dos desenvolvedores é competente o suficiente para entender que isso não deve ser usado polimorficamente de qualquer maneira.
Minha decisão é absolutamente injustificável? Se sim, por quê? Você pode fornecer uma alternativa que teria membros adicionais na verdade membros, mas não envolveria redigitar toda a interface do vetor? Duvido, mas se puder, serei feliz.
Além disso, além do fato de que algum idiota pode escrever algo como
std::vector<int>* p = new MyVector
existe algum outro perigo realista no uso do MyVector? Ao dizer realista, descarto coisas como imaginar uma função que leva um ponteiro para o vetor ...
Bem, eu declarei o meu caso. Eu pequei. Agora cabe a você me perdoar ou não :)
std::vector
A interface do @ Jim: é bastante grande e, quando o C ++ 1x aparecer, ele se expandirá bastante. É muito para digitar e mais para expandir em alguns anos. Penso que esta é uma boa razão para considerar herança em vez de contenção - se alguém seguir a premissa de que essas funções devem ser membros (o que duvido). A regra para não derivar de contêineres STL é que eles não são polimórficos. Se você não os estiver usando dessa maneira, não se aplicará.Respostas:
Na verdade, não há nada errado com a herança pública de
std::vector
. Se você precisar, faça isso.Eu sugeriria fazer isso apenas se for realmente necessário. Somente se você não puder fazer o que deseja com funções gratuitas (por exemplo, deve manter algum estado).
O problema é que
MyVector
é uma nova entidade. Isso significa que um novo desenvolvedor de C ++ deve saber o que diabos é antes de usá-lo. Qual é a diferença entrestd::vector
eMyVector
? Qual é o melhor para usar aqui e ali? E se eu precisar moverstd::vector
paraMyVector
? Posso apenas usarswap()
ou não?Não produza novas entidades apenas para fazer algo parecer melhor. Essas entidades (especialmente, tão comuns) não vão viver no vácuo. Eles viverão em ambiente misto com entropia constantemente aumentada.
fonte
MyVector
e tente transmiti-lo para funções que aceitamstd::vector&
oustd::vector*
. Se houver algum tipo de atribuição de cópia envolvida no uso de std :: vector * ou std :: vector &, teremos problemas de divisão em que os novos membros de dadosMyVector
não serão copiados. O mesmo seria válido para chamar swap através de um ponteiro / referência base. Costumo pensar que qualquer tipo de hierarquia de herança que corre o risco de cortar objetos é ruim.std::vector
destruidor de não évirtual
, portanto você nunca deve herdar deleTodo o STL foi projetado de forma que algoritmos e contêineres sejam separados .
Isso levou a um conceito de diferentes tipos de iteradores: iteradores const, iteradores de acesso aleatório etc.
Portanto, recomendo que você aceite esta convenção e projete seus algoritmos de forma que eles não se importem com o contêiner em que estão trabalhando - e precisariam apenas de um tipo específico de iterador para o qual precisariam executar suas tarefas. operações.
Além disso, deixe-me redirecioná-lo para algumas boas observações de Jeff Attwood .
fonte
O principal motivo para não herdar
std::vector
publicamente é a ausência de um destruidor virtual que efetivamente o impeça de usar polimorficamente os descendentes. Em particular, você não tem permissão paradelete
umstd::vector<T>*
que realmente aponte para um objeto derivado (mesmo que a classe derivada não adicione membros), mas o compilador geralmente não pode avisá-lo sobre isso.A herança privada é permitida sob essas condições. Por isso, recomendo usar a herança privada e encaminhar os métodos necessários do pai, como mostrado abaixo.
Você deve primeiro refatorar seus algoritmos para abstrair o tipo de contêiner em que eles estão operando e deixá-los como funções de modelo livre, conforme indicado pela maioria dos respondentes. Isso geralmente é feito fazendo um algoritmo aceitar um par de iteradores em vez de contêiner como argumentos.
fonte
vector
O armazenamento alocado do não é o problema - afinal,vector
o destruidor do sistema seria chamado corretamente através de um ponteiro paravector
. É justo que os proíbe padrãodelete
ing loja livre objetos através de uma expressão classe base. O motivo é certamente que o mecanismo de (des) alocação pode tentar inferir o tamanho do pedaço de memória para liberar dodelete
operando, por exemplo, quando existem várias arenas de alocação para objetos de determinados tamanhos. Essa restrição não se aplica à destruição normal de objetos com duração de armazenamento estático ou automático.Se você está considerando isso, claramente já matou os pedantes de idiomas em seu escritório. Com eles fora do caminho, por que não fazer
Isso contornará todos os erros possíveis que podem resultar do aumento acidental de sua classe MyVector, e você ainda pode acessar todas as operações de vetor apenas adicionando um pouco
.v
.fonte
O que você espera conseguir? Apenas fornecendo alguma funcionalidade?
A maneira idiomática do C ++ para fazer isso é escrever apenas algumas funções gratuitas que implementam a funcionalidade. Provavelmente, você realmente não precisa de um std :: vector, especificamente para a funcionalidade que está implementando, o que significa que você está realmente perdendo a capacidade de reutilização tentando herdar do std :: vector.
Eu recomendo fortemente que você analise a biblioteca e os cabeçalhos padrão e medite sobre como eles funcionam.
fonte
front
eback
também funções. :) (Considere também o exemplo de freebegin
eend
em C ++ 0x e boost.)Penso que poucas regras devem ser seguidas às cegas 100% do tempo. Parece que você pensou bastante e está convencido de que esse é o caminho a seguir. Portanto, a menos que alguém tenha boas razões específicas para não fazer isso, acho que você deve prosseguir com seu plano.
fonte
Não há razão para herdar, a
std::vector
menos que se queira criar uma classe que funcione de maneira diferentestd::vector
, porque ela lida com os detalhes ocultos dastd::vector
definição de uma maneira, ou a menos que tenha razões ideológicas para usar os objetos dessa classe no lugar destd::vector
são os No entanto, os criadores do padrão em C ++ não forneceramstd::vector
nenhuma interface (na forma de membros protegidos) que essa classe herdada pudesse tirar proveito para melhorar o vetor de uma maneira específica. Na verdade, eles não tinham como pensar em nenhum aspecto específico que pudesse precisar de extensão ou ajustar a implementação adicional, portanto, não precisavam pensar em fornecer nenhuma interface desse tipo para nenhum propósito.As razões para a segunda opção podem ser apenas ideológicas, porque
std::vector
s não são polimórficas e, caso contrário, não há diferença se você expõestd::vector
a interface pública da via herança pública ou associação pública. (Suponha que você precise manter algum estado em seu objeto para não se safar das funções livres). Em uma nota menos sólida e do ponto de vista ideológico, parece questd::vector
s são uma espécie de "idéia simples"; portanto, qualquer complexidade na forma de objetos de diferentes classes possíveis em seu lugar ideologicamente não faz sentido.fonte
Em termos práticos: Se você não possui nenhum membro de dados em sua classe derivada, não possui problemas, nem mesmo no uso polimórfico. Você só precisa de um destruidor virtual se os tamanhos da classe base e da classe derivada forem diferentes e / ou você tiver funções virtuais (o que significa uma tabela em V).
MAS, em teoria: De [expr.delete] no C ++ 0x FCD: Na primeira alternativa (excluir objeto), se o tipo estático do objeto a ser excluído for diferente do seu tipo dinâmico, o tipo estático deve ser um classe base do tipo dinâmico do objeto a ser excluído e o tipo estático deve ter um destruidor virtual ou o comportamento é indefinido.
Mas você pode derivar privadamente do std :: vector sem problemas. Eu usei o seguinte padrão:
fonte
[expr.delete]
no C ++ 0x FCD: <quote> Na primeira alternativa (excluir objeto), se o tipo estático do objeto a ser excluído for diferente de seu tipo dinâmico, o tipo estático deve ser uma classe base do tipo dinâmico do objeto a ser excluído e o tipo estático deve ter um destruidor virtual ou o comportamento é indefinido. </quote>Se você seguir um bom estilo C ++, a ausência de função virtual não é o problema, mas o fatiamento (consulte https://stackoverflow.com/a/14461532/877329 )
Por que a ausência de funções virtuais não é o problema? Porque uma função não deve tentar
delete
qualquer ponteiro que recebe, uma vez que não a possui. Portanto, se seguir políticas rígidas de propriedade, não serão necessários destruidores virtuais. Por exemplo, isso está sempre errado (com ou sem destruidor virtual):Por outro lado, isso sempre funcionará (com ou sem destruidor virtual):
Se o objeto for criado por uma fábrica, a fábrica também deve retornar um ponteiro para um deleter de trabalho, que deve ser usado em vez de
delete
, pois a fábrica pode usar seu próprio heap. O chamador pode obter a forma de umshare_ptr
ouunique_ptr
. Em suma, nãodelete
qualquer coisa que você não obter directamente a partirnew
.fonte
Sim, é seguro desde que você tome cuidado para não fazer coisas que não são seguras ... Acho que nunca vi alguém usar um vetor com um novo, então na prática você provavelmente ficará bem. No entanto, não é o idioma comum em c ++ ....
Você é capaz de fornecer mais informações sobre o que são os algoritmos?
Às vezes, você acaba seguindo um caminho com um design e, em seguida, não consegue ver os outros caminhos que você pode ter seguido - o fato de que você afirma ter que vetorizar com 10 novos algoritmos, toca campainhas de alarme para mim - existem realmente 10 fins gerais algoritmos que um vetor pode implementar ou você está tentando criar um objeto que seja um vetor de uso geral E que contenha funções específicas do aplicativo?
Certamente não estou dizendo que você não deve fazer isso, é apenas que, com as informações que você deu, os alarmes estão tocando, o que me faz pensar que talvez algo esteja errado com suas abstrações e que haja uma maneira melhor de conseguir o que você deseja. quer.
fonte
Também herdei de
std::vector
recentemente e achei muito útil e até agora não tive problemas com isso.Minha classe é uma classe de matriz esparsa, o que significa que eu preciso armazenar meus elementos da matriz em algum lugar, ou seja, em um
std::vector
. Minha razão para herdar foi que eu estava com preguiça de escrever interfaces para todos os métodos e também estou fazendo interface com a classe para Python via SWIG, onde já existe um bom código de interfacestd::vector
. Achei muito mais fácil estender esse código de interface para minha classe, em vez de escrever um novo a partir do zero.O único problema que eu posso ver com a abordagem não é tanto com o destrutor não-virtual, mas sim alguns outros métodos, o que eu gostaria de sobrecarga, tais como
push_back()
,resize()
,insert()
etc. herança Privada poderia realmente ser uma boa opção.Obrigado!
fonte
Aqui, deixe-me apresentar mais duas maneiras de fazer o que você deseja. Uma é outra maneira de quebrar
std::vector
, outra é a maneira de herdar sem dar aos usuários a chance de quebrar qualquer coisa:std::vector
sem escrever muitos wrappers de função.std::vector
e evitar o problema do dtor.fonte
É garantido que essa pergunta produz embreagem de pérola sem fôlego, mas, de fato, não há razão defensável para evitar, ou "multiplicar desnecessariamente entidades" para evitar derivação de um contêiner Standard. A expressão mais simples e mais curta possível é a mais clara e a melhor.
Você precisa exercer todo o cuidado usual em relação a qualquer tipo derivado, mas não há nada de especial no caso de uma base da Norma. Substituir uma função de membro base pode ser complicado, mas seria imprudente fazer com qualquer base não virtual, portanto, não há muito especial aqui. Se você adicionasse um membro de dados, precisaria se preocupar em fatiar se o membro tivesse que ser consistente com o conteúdo da base, mas novamente é o mesmo para qualquer base.
O local em que achei derivar de um contêiner padrão particularmente útil é adicionar um único construtor que faça exatamente a inicialização necessária, sem chance de confusão ou seqüestro por outros construtores. (Estou olhando para você, construtores initialization_list!) Então, você pode usar livremente o objeto resultante, fatiado - passe-o por referência a algo que espera a base, mova-o para uma instância da base, o que você tem. Não há casos extremos com os quais se preocupar, a menos que isso o incomode vincular um argumento de modelo à classe derivada.
Um local onde essa técnica será imediatamente útil no C ++ 20 é a reserva. Onde poderíamos ter escrito
nós podemos dizer
e depois, mesmo como alunos,
(de acordo com a preferência) e não precisa escrever um construtor apenas para chamar reserva () neles.
(A razão pela qual
reserve_in
, tecnicamente, precisa aguardar o C ++ 20 é que os Padrões anteriores não exigem que a capacidade de um vetor vazio seja preservada entre as jogadas. Isso é reconhecido como um descuido e pode-se esperar uma correção razoável como um defeito a tempo para o ano 20. Também podemos esperar que a correção seja, efetivamente, datada dos Padrões anteriores, porque todas as implementações existentes preservam a capacidade em todos os movimentos; os Padrões simplesmente não o exigiram. a reserva de armas é quase sempre apenas uma otimização.)Alguns argumentam que o caso de
reserve_in
é melhor servido por um modelo de função livre:Essa alternativa é certamente viável - e pode até, às vezes, ser infinitesimalmente mais rápida, por causa do * RVO. Mas a escolha da derivação ou função livre deve ser feita por seus próprios méritos, e não por superstições infundadas (heh!) Sobre derivar de componentes Padrão. No exemplo de uso acima, apenas o segundo formulário funcionaria com a função livre; embora fora do contexto de classe, poderia ser escrito de forma um pouco mais concisa:
fonte