Eu tenho uma classe que representa uma lista de pessoas.
class AddressBook
{
public:
AddressBook();
private:
std::vector<People> people;
}
Eu quero permitir que os clientes iterem sobre o vetor de pessoas. O primeiro pensamento que tive foi simplesmente:
std::vector<People> & getPeople { return people; }
No entanto, não quero vazar os detalhes de implementação para o cliente . Talvez eu queira manter certos invariantes quando o vetor é modificado e perco o controle sobre esses invariantes quando vazo a implementação.
Qual é a melhor maneira de permitir a iteração sem vazar os internos?
begin()
eend()
é perigoso porque (1) esses tipos são iteradores de vetores (classes) que impedem que um alterne para outro contêiner, como aset
. (2) Se o vetor for modificado (por exemplo, crescido ou alguns itens apagados), alguns ou todos os iteradores do vetor poderão ter sido invalidados.Respostas:
permitir a iteração sem vazar os internos é exatamente o que o padrão do iterador promete. Claro que isso é principalmente teoria, então aqui está um exemplo prático:
Você fornece padrões
begin
eend
métodos, assim como seqüências no STL e os implementa simplesmente encaminhando para o método do vetor. Isso vaza alguns detalhes da implementação, ou seja, você está retornando um iterador de vetor, mas nenhum cliente sensato jamais deve depender disso, portanto isso não é uma preocupação. Eu mostrei todas as sobrecargas aqui, mas é claro que você pode começar apenas fornecendo a versão const se os clientes não puderem alterar nenhuma entrada de Pessoas. O uso da nomeação padrão traz benefícios: quem lê o código imediatamente sabe que ele fornece iteração 'padrão' e, como tal, trabalha com todos os algoritmos comuns, com base em intervalos para loops etc.fonte
begin()
eend()
que apenas para a frente para o vetor dobegin()
eend()
permite que o usuário modificar os elementos do próprio vetor, talvez usandostd::sort()
. Dependendo dos invariantes que você está tentando preservar, isso pode ou não ser aceitável. Fornecerbegin()
eend()
, no entanto, é necessário dar suporte ao C ++ 11 baseado em intervalo para loops.Se a iteração é tudo que você precisa, talvez um wrapper
std::for_each
seja suficiente:fonte
const
iteração. Ofor_each()
é umaconst
função de membro. Portanto, o membropeople
é visto comoconst
. Portanto,begin()
eend()
irá sobrecarregar comoconst
. Portanto, eles retornarãoconst_iterator
s parapeople
. Portanto,f()
receberá umPeople const&
. Escrevendocbegin()
/cend()
aqui vai mudar nada, na prática, embora como um usuário obsessivo deconst
eu poderia argumentar que ainda vale a pena fazer, como (a) por que não; é apenas 2 caracteres, (b) I como dizer o que quero dizer, pelo menos, comconst
, (c) que protege contra acidentalmente colar em algum lugar nãoconst
, etc.Você pode usar o idioma pimpl e fornecer métodos para iterar sobre o contêiner.
No cabeçalho:
Na fonte:
Dessa forma, se seu cliente usar o typedef do cabeçalho, ele não perceberá que tipo de contêiner você está usando. E os detalhes da implementação estão completamente ocultos.
fonte
Pode-se fornecer funções-membro:
Que permitem o acesso sem expor detalhes da implementação (como contiguidade) e os utilizam em uma classe de iterador:
Os iteradores podem ser retornados pelo catálogo de endereços da seguinte maneira:
Você provavelmente precisaria detalhar a classe do iterador com características etc., mas acho que isso fará o que você pediu.
fonte
se você deseja uma implementação exata das funções do std :: vector, use a herança privada como abaixo e controle o que está exposto.
Edit: Isso não é recomendado se você também deseja ocultar a estrutura de dados interna, isto é, std :: vector
fonte
vector
outros, que você nunca deseja usar, mas deve herdar?) e talvez ativamente perigoso (e se a classe herdada preguiçosamente puder ser excluída por meio de um ponteiro para esse tipo de base em algum lugar, mas [irresponsável] não protegeu contra a destruição de um derivado obj através tais ponteiro um, assim simplesmente destruir é UB)?