Por que std :: queue :: pop retorna valor.?

123

Passei por esta página, mas não sou capaz de entender o motivo. Lá é mencionado que

"é mais sensato não retornar nenhum valor e exigir que os clientes usem front () para inspecionar o valor na frente da fila"

Mas a inspeção de um elemento de front () também exigia que esse elemento fosse copiado em lvalue. Por exemplo, neste segmento de código

std::queue<int> myqueue;
int myint;
int result;
std::cin >> myint;
myqueue.push (myint);

/ * aqui temporário será criado no RHS que será atribuído ao resultado e, se retornos por referência, o resultado será invalidado após a operação pop * /

result = myqueue.front();  //result.
std::cout << ' ' << result;
myqueue.pop();

na quinta linha, o objeto cout cria primeiro uma cópia do myqueue.front () e depois atribui isso ao resultado. Então, qual é a diferença, a função pop poderia ter feito a mesma coisa.

encadernador
fonte
Porque é implementado dessa maneira (ou seja, void std::queue::pop();).
101010
A pergunta foi respondida, mas como um sidenote: se você realmente quer um pop que retorna, ele pode ser facilmente implementado com uma função livre: ideone.com/lnUzf6
eerorika
1
Seu link é para a documentação do STL. Mas você está perguntando sobre a biblioteca padrão C ++. Coisas diferentes.
Juanchopanza
5
"Mas inspecionar um elemento front()também requer que esse elemento seja copiado em lvalue" - não, não é. frontretorna uma referência, não um valor. Você pode inspecionar o valor a que se refere sem copiá-lo.
Mike Seymour
1
@KeminZhou, o modelo que você descreve requer uma cópia. Talvez. Se você deseja multiplexar o consumo da fila, sim, você deve fazer uma cópia antes de liberar o bloqueio na fila. No entanto, se você se preocupa apenas em separar entrada e saída, não precisa de uma trava para inspecionar a frente. Você pode esperar para bloquear até terminar de consumi-lo e precisar ligar pop(). Se você usar std::queue<T, std::list<T>>, não há nenhuma preocupação sobre a referência fornecida front()ser invalidada por a push(). Mas você deve conhecer seu padrão de uso e documentar suas restrições.
jwm

Respostas:

105

Então, qual é a diferença, a função pop poderia ter feito a mesma coisa.

Poderia realmente ter feito a mesma coisa. O motivo disso não ocorreu é porque um pop que retornou o elemento popped não é seguro na presença de exceções (tendo que retornar por valor e, assim, criar uma cópia).

Considere este cenário (com uma implementação pop ingênua / inventada, para ilustrar meu argumento):

template<class T>
class queue {
    T* elements;
    std::size_t top_position;
    // stuff here
    T pop()
    {
        auto x = elements[top_position];
        // TODO: call destructor for elements[top_position] here
        --top_position;  // alter queue state here
        return x;        // calls T(const T&) which may throw
    }

Se o construtor de cópias de T lançar no retorno, você já alterou o estado da fila ( top_positionna minha implementação ingênua) e o elemento será removido da fila (e não retornado). Para todas as intenções e propósitos (não importa como você captura a exceção no código do cliente), o elemento na parte superior da fila é perdido.

Essa implementação também é ineficiente no caso em que você não precisa do valor pop-up (isto é, cria uma cópia do elemento que ninguém usará).

Isso pode ser implementado com segurança e eficiência, com duas operações separadas ( void pope const T& front()).

utnapistim
fonte
hmmm ... isso faz sentido #
cbinder
37
Nota do C ++ 11: se T tiver um construtor de movimentos barato, exceto no caso de exceção (que geralmente é o caso do tipo de objeto colocado nas pilhas), o retorno por valor será eficiente e protegido contra exceções.
Roman L
9
@ DavidRodríguez-dribeas: Não estou sugerindo alterações, o que quero dizer é que algumas das desvantagens mencionadas se tornam menos um problema com o C ++ 11.
Roman L
11
Mas por que é chamado pop? isso é bastante contra-intuitivo. Poderia ser nomeado drope, então, fica claro para todos que ele não aparece o elemento, mas o solta ...
UeliDeSchwert
12
@utnapistim: a operação "pop" de fato sempre foi pegar o elemento top de uma pilha e devolvê-lo. Quando me deparei com as stl stl, fiquei surpreso, para dizer o mínimo, por popnão retornar nada. Veja, por exemplo, na wikipedia .
Cris Luengo
34

A página à qual você vinculou responde à sua pergunta.

Para citar toda a seção relevante:

Alguém pode se perguntar por que pop () retorna nulo, em vez de value_type. Ou seja, por que alguém deve usar front () e pop () para examinar e remover o elemento na frente da fila, em vez de combinar os dois em uma única função de membro? De fato, há uma boa razão para esse design. Se pop () retornasse o elemento front, ele teria que retornar por valor e não por referência: o retorno por referência criaria um ponteiro pendente. O retorno por valor, no entanto, é ineficiente: envolve pelo menos uma chamada de construtor de cópia redundante. Como é impossível para pop () retornar um valor de maneira a ser eficiente e correta, é mais sensato não retornar nenhum valor e exigir que os clientes usem front () para inspecionar o valor em a frente da fila.

O C ++ é projetado com eficiência em mente, sobre o número de linhas de código que o programador precisa escrever.

BPF2010
fonte
16
Talvez, mas a verdadeira razão é que é impossível implementar uma versão segura de exceção (com a forte garantia) para uma versão da popqual retorna um valor.
James Kanze
5

pop não pode retornar uma referência ao valor removido, pois está sendo removido da estrutura de dados; portanto, a que referência deve se referir? Pode retornar por valor, mas e se o resultado de pop não for armazenado em nenhum lugar? Então se perde tempo copiando o valor desnecessariamente.

Neil Kirk
fonte
4
O verdadeiro motivo é a segurança de exceção. Não há uma maneira segura de ter uma "transação" com a pilha (o elemento permanece na pilha ou é devolvido a você) se popretornos e o ato de retornar podem resultar em uma exceção. Obviamente, seria necessário remover o elemento antes de devolvê-lo e, se algo jogar, o elemento poderá ser irrevogavelmente perdido.
Jon
1
Essa preocupação ainda se aplica a um construtor de movimento noexcept? (Claro 0 cópias são mais eficientes que dois movimentos, mas que poderia abrir a porta para uma segura exceção, eficiente combinada frente + POP)
peppe
1
@peppe, você pode retornar por valor se souber que value_typepossui um construtor de movimentação de nothrow, mas a interface da fila seria diferente dependendo do tipo de objeto armazenado nela, o que não seria útil.
Jonathan Wakely
1
@peppe: Isso seria seguro, mas a pilha é uma classe de biblioteca genérica e, portanto, quanto mais ela assume sobre o tipo de elemento, menos útil é.
Jon
Eu sei que o STL não permitiria isso, mas isso significa que se pode construir a sua própria classe fila com blackjack e ^ W ^ W ^ W com esse recurso :)
peppe
3

Com a implementação atual, isso é válido:

int &result = myqueue.front();
std::cout << result;
myqueue.pop();

Se pop retornasse uma referência, assim:

value_type& pop();

Em seguida, o código a seguir pode falhar, pois a referência não é mais válida:

int &result = myqueue.pop();
std::cout << result;

Por outro lado, se ele retornasse um valor diretamente:

value_type pop();

Então você precisaria fazer uma cópia para esse código funcionar, o que é menos eficiente:

int result = myqueue.pop();
std::cout << result;
Jan Rüegg
fonte
1

A partir do C ++ 11, seria possível arquivar o comportamento desejado usando a semântica de movimentação. Like pop_and_move. Portanto, o construtor de cópias não será chamado e o desempenho dependerá apenas do construtor de movimentação.

Vyacheslav Putsenko
fonte
2
Não, mover semântica não magicamente torna a popexceção segura.
LF
0

Você pode fazer isso totalmente:

std::cout << ' ' << myqueue.front();

Ou, se você quiser o valor em uma variável, use uma referência:

const auto &result = myqueue.front();
if (result > whatever) do_whatever();
std::cout << ' ' << result;

Além disso: o termo 'mais sensível' é uma forma subjetiva de 'examinamos os padrões de uso e descobrimos mais necessidade de uma divisão'. (Tenha certeza: a linguagem C ++ não está evoluindo levemente ...)

xtofl
fonte
0

Eu acho que a melhor solução seria adicionar algo como

std::queue::pop_and_store(value_type& value);

onde value receberá o valor popped.

A vantagem é que ele pode ser implementado usando um operador de atribuição de movimento, enquanto usar front + pop fará uma cópia.

Masse Nicolas
fonte