Pergunta

Eu estou tentando obter minha cabeça em torno tuplas (graças @ litb), ea sugestão comum para o seu uso é para funções que retornam> 1 valor.

Isso é algo que eu normalmente usar um struct para, e eu não posso compreender as vantagens de tuplas neste caso -. Parece uma abordagem passível de erro para o terminal preguiçoso

Emprestando um exemplo , eu uso este

struct divide_result {
    int quotient;
    int remainder;
};

Usando uma tupla, você teria

typedef boost::tuple<int, int> divide_result;

Mas sem ler o código da função que você está chamando (ou os comentários, se você estiver suficientemente idiota para confiar neles) você não tem idéia que int é quociente e vice-versa. Parece um pouco como ...

struct divide_result {
    int results[2]; // 0 is quotient, 1 is remainder, I think
};

... que não me enchem de confiança.

Então, o são as vantagens de tuplas mais estruturas que compensam a ambigüidade?

Foi útil?

Solução

tuplas

Eu acho que eu concordo com você que a questão com a posição que corresponde ao que variável pode apresentar confusão. Mas eu acho que existem dois lados. Um deles é o Chamada-side e a outra é a callee-side :

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

Eu acho que é claro como cristal o que temos, mas pode tornar-se confuso se você tiver que retornar mais valores ao mesmo tempo. Uma vez programador do chamador tem olhado a documentação de div, ele saberá que posição é o que, e pode escrever código eficaz. Como regra geral, eu diria para não retornar mais de 4 valores ao mesmo tempo. Para além de qualquer coisa, preferem um struct.

saída de parâmetros

Parâmetros de saída pode ser usado também, é claro:

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

Agora eu acho que ilustra como tuplas são melhores do que os parâmetros de saída. Podemos ter misturado a entrada de div com sua saída, apesar de não ganhar qualquer vantagem. Pior, deixamos o leitor de que o código em dúvida sobre o que poderia ser o real valor de retorno de div ser. Há são exemplos maravilhosos quando os parâmetros de saída são úteis. Na minha opinião, você deve usá-los somente quando você não tem outra maneira, porque o valor de retorno já está tomada e não pode ser alterado a qualquer uma tupla ou struct. operator>> é um bom exemplo de onde você usar parâmetros de saída, porque o valor de retorno já está reservada para o rio, para que você possa chamadas cadeia operator>>. Se você tem para não fazer com os operadores, e o contexto não é cristalina, eu recomendo que você usar ponteiros, para sinalizar ao lado da chamada que o objeto é realmente usado como um parâmetro de saída, além de comentários se necessário.

retornar uma struct

A terceira opção é usar um struct:

div_result d = div(10, 3);

Eu acho que definitivamente ganha o prêmio de clareza . Mas note que você ainda tem que acessar o resultado dentro dessa estrutura, eo resultado não é "exposto" em cima da mesa, como foi o caso para os parâmetros de saída ea tupla usada com tie.

Eu acho que um ponto importante nos dias de hoje é fazer tudo o mais genérico possível. Então, digamos que você tem uma função que pode imprimir tuplas. Você pode apenas fazer

cout << div(10, 3);

E se o resultado apresentado. Eu acho que tuplas, por outro lado, ganhar claramente para a sua versátil natureza. Fazer isso com div_result, você precisa operador de sobrecarga <<, ou necessidade de saída de cada membro separadamente.

Outras dicas

Outra opção é usar um impulso Fusão mapa (código não testado):

struct quotient;
struct remainder;

using boost::fusion::map;
using boost::fusion::pair;

typedef map<
    pair< quotient, int >,
    pair< remainder, int >
> div_result;

Você pode acessar os resultados relativamente intuitivamente:

using boost::fusion::at_key;

res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);

Há outras vantagens também, como a capacidade de iterate sobre os campos do mapa, etc etc. Veja a doco para obter mais informações.

Com tuplas, você pode usar tie, que às vezes é bastante útil: std::tr1::tie (quotient, remainder) = do_division ();. Isso não é tão fácil com estruturas. Em segundo lugar, ao usar código do modelo, às vezes é mais fácil de contar com pares do que para adicionar mais um typedef para o tipo de estrutura.

E se os tipos são diferentes, então um par / tupla é realmente não é pior do que um struct. Pense, por exemplo pair<int, bool> readFromFile(), onde o int é o número de bytes ler e bool é se a EOF foi atingido. Adicionando um struct, neste caso, parece um exagero para mim, especialmente porque não há nenhuma ambigüidade aqui.

Tuples são muito úteis em linguagens como ML ou Haskell.

Em C ++, sua sintaxe torna menos elegante, mas pode ser útil nas seguintes situações:

  • você tem uma função que deve retornar mais de um argumento, mas o resultado é "local" para o chamador e o receptor; você não deseja definir uma estrutura apenas para este

  • Você pode usar a função de laço para fazer uma forma muito limitada de correspondência de padrão "a la ML", que é mais elegante do que usando uma estrutura para o mesmo fim.

  • eles vêm com predefinidos

I tendem a usar tuplas em conjunto com typedefs para pelo menos aliviar parcialmente o 'inominável tuple' problema. Por exemplo, se eu tivesse uma estrutura de grade, em seguida:

//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;

Então eu uso o tipo nomeado como:

grid_index find(const grid& g, int value);

Este é um exemplo um pouco artificial, mas eu acho que a maior parte do tempo ela atinge um meio termo entre a legibilidade, clareza e facilidade de uso.

Ou no seu exemplo:

//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);

Uma característica de tuplas que você não tem com estruturas está em sua inicialização. Considere algo como o seguinte:

struct A
{
  int a;
  int b;
};

A menos que você escrever uma equivalente make_tuple ou construtor, em seguida, usar essa estrutura como um parâmetro de entrada que você primeiro tem que criar um objeto temporário:

void foo (A const & a)
{
  // ...
}

void bar ()
{
   A dummy = { 1, 2 };
   foo (dummy);
}

Não é tão ruim, no entanto, levar o caso onde a manutenção adiciona um novo membro para o nosso struct por qualquer motivo:

struct A
{
  int a;
  int b;
  int c;
};

As regras de inicialização agregada, na verdade, significa que o nosso código vai continuar a compilar sem mudança. Por isso, tem que procurar por todos os usos deste struct e atualizá-los, sem qualquer ajuda do compilador.

Contraste isso com uma tupla:

typedef boost::tuple<int, int, int> Tuple;
enum {
  A
  , B
  , C
};

void foo (Tuple const & p) {
}

void bar ()
{
  foo (boost::make_tuple (1, 2));  // Compile error
}

O compilador não pode initailize "Tuple" com o resultado de make_tuple, e assim gera o erro que permite que você especifique os valores corretos para o terceiro parâmetro.

Finalmente, a outra vantagem de tuplas é que eles permitem que você escrever código que itera sobre cada valor. Isso simplesmente não é possível utilizar um struct.

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}

impede que o código que está sendo repleta de muitas definições de struct. É mais fácil para a pessoa escrever o código, e por outro a usá-lo quando você apenas documentar o que cada elemento na tupla é, em vez de escrever o seu próprio struct / fazer as pessoas olhar a definição struct.

Tuples será mais fácil de escrever - não há necessidade de criar uma nova estrutura para cada função que retorna algo. Documentação sobre o que vai para onde vai a documentação função, que será necessário de qualquer maneira. Para utilizar a função de um terá de ler a documentação função em qualquer caso e tupla será explicado lá.

Eu concordo com você 100% Roddy.

Para retornar vários valores de um método, você tem várias outras opções do que tuplas, qual é o melhor depende de seu caso:

  1. Criar uma nova estrutura. Isso é bom quando os valores múltiplas você está retornando são relacionadas , e é apropriado para criar uma nova abstração. Por exemplo, eu acho "divide_result" é uma boa abstração geral, e passando esta entidade em torno faz seu código muito mais claro do que apenas passar uma tupla sem nome redor. Você poderia, então, criar métodos que operam no este novo tipo, convertê-lo para outros tipos numéricos, etc.

  2. Usando parâmetros "Out". Passar vários parâmetros por referência, e retornar vários valores, atribuindo ao parâmetro cada fora. Isto é apropriado quando o método retorna vários não relacionados peças de informação. Criando uma nova estrutura, neste caso, seria um exagero, e com parâmetros fora você enfatizar este ponto, além de cada item recebe o nome que merece.

Tuples são maus.

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