Pergunta

Primeiro de tudo peço desculpas para o longo levam a uma pergunta tão simplista.

Estou implementando uma classe que serve como um indicador muito tempo um dimensional numa curva espaço de enchimento ou o n-tuplo que representa a coordenada cartesiana que corresponde ao índice.

class curvePoint
{
public:
    friend class curveCalculate;

    //Construction and Destruction
    curvePoint(): point(NULL), dimensions(0) {}
    virtual ~curvePoint(){if(point!=NULL) delete[] point;}

    //Mutators
    void convertToIndex(){ if(isTuple()) calc(this); }
    void convertToTuple(){ if(isIndex()) calc(this); }
    void setTuple(quint16 *tuple, int size);
    void setIndex(quint16 *index, int size);
    void setAlgorithm(curveType alg){algorithm = alg;}

    //Inspectors
    bool isIndex(){return current==Index;}
    bool isTuple(){return current==Tuple;}
    size_t size(){return dimensions;}
    quint16 operator[](size_t index);

    enum curveType{HilbertCurve, ZCurve, GrayCodeCurve};
    enum status{Index, Tuple};

private:
    curveCalculate calc;
    curveType algorithm;
    quint16 *point;
    size_t dimensions;
    status current;
};

(O comprimento da matriz apontado por ponto dimensões )

De qualquer forma na implementação do operador [] Eu queria saber qual o melhor método para alcançar verificação de limites é. Eu quero evitar lançar exceções, se possível, e toda a gama de valores é utilizável para cada número na matriz para um valor especial para retornar em caso de um erro de falta de limites não é possível qualquer um;

Eu estava pensando em algo como isso, porém implementado na definição da classe:

quint16 curvePoint::operator[](size_t index)
{
    return point[ index % dimensions ];
}

Isso faz com que nós nunca deixar os limites da matriz e se bem documentado Eu acho que seria bom; No entanto, estou leary desta implementação particular.

Será que este olhar aceitável para os outros? Existe alguma outra maneira de fazer verificação de limites enquanto ainda satisfazendo minhas limitações?

Edit: Cálculo de coisas como curvas de Hilbert etc são altamente confuso o suficiente, confuso que eu não não quero a interface adicional para as bibliotecas STL no caminho.

Além disso, porque eu vou ter que converter muitos milhares deles cada vez que o banco de dados multidimensional é consultado Eu não quero que o custo adicional das chamadas de função STL no mix, se possível.

Eu um pouco como a idéia do assert; mas, se bem me lembro que as interrupções de compilações não é?

Suponho que posso usar exceções, que parece ser o que todo mundo está torcendo para, mas eu estou usando as bibliotecas Qt e essas exceções evitar para desempenho e portabilidade e eu estava esperando para fazer o mesmo.

Foi útil?

Solução

A solução mais fácil é fazer como C ++ em si faz. Isso limita a quantidade de surpresas que os usuários irão experimentar.

C ++ em si é bastante consistente. Tanto o built-in [] em ponteiros e std::vector::operator[] têm um comportamento indefinido se você usar um out-of-bound índice da matriz. Se você quiser verificação de limites, ser explícito e uso std::vector::at

Portanto, se você fazer o mesmo para a sua classe, você pode documentar o comportamento fora-de-bound como "padrão".

Outras dicas

De qualquer forma na implementação de operador [] Eu queria saber o que a melhor método para alcançar verificação de limites é. Eu quero evitar arremesso exceções, se possível, ea gama de valores é utilizável para cada número da matriz para um especial valor para retornar em caso de uma saída de limites de erro não é possível qualquer um;

Em seguida, as restantes opções são:

  • projeto flexível. O que você fez. "Consertar" a entrada inválida para que ele tenta fazer algo que faz sentido. Vantagem: A função não irá falhar. Desvantagem: Clueless chamadores que acessam um fora do elemento limites vai ter um mentira como resultado. Imagine um edifício de 10 andares, com pisos 1 a 10:

Você: "Quem vive no 3º andar"

Me:. "Mary"

Você: "Quem vive no 9º andar"

Me:. "Joe"

Você: "Quem vive no chão 1,203rd"

Me: (Wait ... 1,203% de 10 = 3 ...) > "Maria" .

Você: "Uau, Mary deve desfrutar de excelentes vistas a partir lá em cima . então, ela possui dois apartamentos, então? "

  • A saída de bool parâmetro indica sucesso ou fracasso. Essa opção geralmente acaba em código não muito utilizável. Muitos usuários irão ignorar o código de retorno. Você ainda é deixado com o que você retornar em outro valor de retorno.

  • Design by contrato. Assert que o chamador está dentro de limites. (Para uma abordagem prática em C ++, consulte uma exceção ou um erro? Por Miro Samek ou simples suporte para programação por contrato em C ++ por Pedro Guerreiro .)

  • Voltar a System.Nullable<quint16> . Opa, espera, isso não é C #. Bem, você poderia retornar um ponteiro para um quint16. Isto, obviamente, tem muitas implicações que não vou discutir aqui e que provavelmente fazem esta opção não utilizável.

As minhas escolhas favoritas são:

  • Para a interface pública de uma biblioteca lançado publicamente: entrada será verificada e uma exceção será lançada. Você descartou essa opção, por isso não é uma opção para você. Ainda é minha escolha para a interface de uma biblioteca lançado publicamente.
  • Para código interno:. Design by contrato

Para mim, esta solução é inaceitável, porque você pode estar escondendo um muito difícil encontrar bug. Jogando uma exceção de gama é o caminho a percorrer, ou pelo menos colocar uma afirmação na função.

Se o que você precisa é algum tipo de variedade "circular" de pontos, então sua solução é ok. No entanto, para mim, parece apenas como esconder missusage de operador de indexação para trás alguma lógica "seguro", então eu seria contra a sua solução proposta.

Se não quiser permitir que o índice de excesso, então você pode verificar e lançar uma exceção.

quint16 curvePoint::operator[](size_t index)
{
    if( index >= dimensions)
    {
       throw std::overflow_error();
    }
    return point[ index ];
}

Se você quer ter menos despesas gerais, você poderia evitar exceção, usando afirmações de tempo de depuração (assumir que o índice previsto é sempre válido):

quint16 curvePoint::operator[](size_t index)
{
    assert( index < dimensions);
    return point[ index ];
}

No entanto, sugiro que, em vez de usar os membros de ponto e dimensão, usar um std :: vector para armazenar os pontos. Ele já tem um acesso baseado índice que você poderia usar:

quint16 curvePoint::operator[](size_t index)
{
    // points is declared as std::vector< quint16> points;
    return points[ index ];
}

Ter um operador [] que nunca falha sons agradáveis, mas pode esconder os erros mais tarde, se uma função chamando usa compensar um ilegal, encontra um valor a partir do início do buffer, e procede como se isso é um valor válido.

O melhor método para conseguir verificação de limites seria adicionar uma declaração.

quint16 curvePoint::operator[](size_t index)
{
    assert(index < dimensions);
    return point[index];
}

Se o seu código já depende de bibliotecas de impulso, você pode querer usar BOOST_ASSERT vez.

Se eu fosse você eu iria seguir o exemplo dado pelo STL.

Neste caso std::vector fornece dois métodos: at que é limites verificado e operator[] o que não é. Isso permite que o cliente possa decidir com versão usar. Eu definitivamente não usaria a % size(), pois isso apenas esconde o bug. No entanto verificação de limites irá adicionar muita sobrecarga para quando iteração sobre uma coleção grande, é por isso que deve ser opcional. Apesar de concordar com outros cartazes que o assert é uma idéia muito boa, pois isso só vai causar um impacto no desempenho em compilações de depuração.

Você também deve considerar o retorno referências e fornecimento de const e não versões const. Estas foram as declarações de função para std::vector :

reference at(size_type _Pos);
const_reference at(size_type _Pos) const;

reference operator[](size_type _Pos);
const_reference operator[](size_type _Pos) const;

Como uma boa regra de ouro se eu não estou certo de como especificar uma API eu olhar para exemplos de como os outros especificar APIs semelhantes. Além disso sempre que eu usar uma API tento julgar ou classificá-la, encontrar os bits que eu gosto e desgosto.

Graças ao comentário sobre o recurso C # no cargo de Daniel Daranas eu consegui descobrir uma solução possível. Como afirmei na minha pergunta que eu estou usando as bibliotecas Qt. Lá para que eu possa usar QVariant. QVariant pode ser definido como um estado inválido que pode ser verificado pela função de recebê-lo. Assim, o código seria algo como:

QVariant curvePoint::operator[](size_t index){
    QVariant temp;
    if(index > dimensions){
        temp = QVariant(QVariant::Invalid);
    }
    else{
        temp = QVariant(point[index]);
    }

    return temp;
}

Claro que isso tem o potencial de inserir um pouco de sobrecarga gnarly para a função para que outra possibilidade é usar um modelo par.

std::pair<quint16, bool> curvePoint::operator[](size_t index){
    std::pair<quint16, bool> temp;
    if(index > dimensions){
        temp.second = false;
    }
    else{
        temp.second = true;
        temp.first = point[index];
    }
    return temp;
}

Ou eu poderia usar um QPair, que tem exatamente a mesma funcionalidade e faria com que a necessidade STL não estar ligado na.

Você poderia talvez adicionar um "fora dos limites" exceção ao operador [] (ou pelo menos uma declaração).

Isto deve capturar quaisquer problemas, especialmente quando a depuração.

A menos que eu estou mal-entendido drasticamente alguma coisa,

return point[ index % dimensions ];

não é a verificação de limites em tudo. Ele está retornando um valor real de uma parte totalmente diferente da linha, o que tornará muito mais difícil de detectar erros.

Eu faria qualquer um:

  1. lançar uma exceção ou uma afirmação (embora você disse que não quer fazê-lo)
  2. Simplesmente dereference ponto passado a matriz de uma forma "natural" (ou seja, simplesmente ignorar qualquer verificação interna). A vantagem sobre o% é que eles estão mais propensos (embora indefinido é indefinido) para obter valores "estranho" e / ou uma violação de acesso

No final, o chamador está violando suas pré-condições, e você pode fazer o que quiser. Mas eu acho que essas são as opções mais razoáveis.

Considere também o que Catalin disse sobre a incorporação de built-in coleções de STL se isso é razoável.

A sua solução seria bom se você estava fornecendo acesso aos pontos de uma forma elíptica. Mas vai levar a erros muito desagradável se você usá-lo para funções geométricas arbitrárias, porque você conscientemente fornecer um valor falso.

O operador módulo funciona surpreendentemente bem para índices de matriz - também implementa índices negativos (isto é, point[-3] = point[dimensions - 3].). Isso é fácil de trabalhar, então eu, pessoalmente, recomendo o operador módulo, desde que ele está bem documentado.

Outra opção é deixar o chamador escolher a política para fora da quadra. Considere o seguinte:

template <class OutOfBoundsPolicy>
quint16 curvePoint::operator[](size_t index)
{
    index = OutOfBoundsPolicy(index, dimensions);
    return point[index];
}

Em seguida, você pode definir várias políticas que o chamador pode escolher. Por exemplo:

struct NoBoundsCheck {
    size_t operator()(size_t index, size_t /* max */) {
        return index;
    }
};

struct WrapAroundIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        return index % max;
    }
};

struct AssertIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        assert(index < max);
        return index % max;
    }
};

struct ThrowIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) throw std::domain_error;
        return index;
    }
};

struct ClampIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) index = max - 1;
        return index;
    }
};
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top