Por que não há problema em retornar um 'vetor' de uma função?

108

Por favor, considere este código. Já vi esse tipo de código várias vezes. wordsé um vetor local. Como é possível retorná-lo de uma função?

Podemos garantir que não morrerá?

 std::vector<std::string> read_file(const std::string& path)
 {
    std::ifstream file("E:\\names.txt");

    if (!file.is_open())
    {
        std::cerr << "Unable to open file" << "\n";
        std::exit(-1);
    }

    std::vector<string> words;//this vector will be returned
    std::string token;

    while (std::getline(file, token, ','))
    {
        words.push_back(token);
    }

    return words;
}
Pranit Kothari
fonte
18
Ele será copiado quando retornar.
songyuanyao
6
Ninguém garante .. Vai morrer, mas depois de copiado.
Maroun
7
Você só terá um problema se sua função retornar uma referência:std::vector<std::string>&
Caduchon
14
@songyuanyao não, ele será movido.
rightfold
15
@songyuanyao Sim. C ++ 11 é o padrão atual, então C ++ 11 é C ++.
rightfold

Respostas:

68

Podemos garantir que não morrerá?

Desde que nenhuma referência seja retornada, é perfeitamente normal fazê-lo. wordsserá movido para a variável que recebe o resultado.

A variável local sairá do escopo. depois de movido (ou copiado).

πάντα ῥεῖ
fonte
2
Mas o vetor é eficiente ou tem alguma preocupação com o desempenho, que pode conter 1000 entradas?
zar
@zadane Isso estava em questão? Também mencionei mover que evitará fazer uma cópia do valor de retorno na verdade (disponível pelo menos com o padrão atual).
πάντα ῥεῖ
2
Não, não realmente na pergunta, mas eu estava procurando uma resposta dessa perspectiva de forma independente. Não sei se postarei minha pergunta, receio que a marquem como uma duplicata desta :)
zar
@zadane "Temo que marquem como uma duplicata deste" Pode muito bem ser. Basta dar uma olhada na resposta com maior votação . Mesmo para implementações mais antigas, você não deve se preocupar, elas serão otimizadas corretamente por esses compiladores de qualquer maneira.
πάντα ῥεῖ
107

Pré C ++ 11:

A função não retornará a variável local, mas sim uma cópia dela. Seu compilador pode, entretanto, realizar uma otimização onde nenhuma ação de cópia real é feita.

Veja esta pergunta e resposta para mais detalhes.

C ++ 11:

A função irá mover o valor. Veja esta resposta para mais detalhes.

Tim Meyer
fonte
2
Ele será movido, não copiado. Isso é garantido.
rightfold
1
Isso também se aplica ao C ++ 10?
Tim Meyer
28
Não existe C ++ 10.
rightfold
C ++ 03 não tinha semântica de movimento (mas a cópia pode ter sido omitida), mas C ++ é C ++ 11 e a questão era sobre C ++.
rightfold
19
Há uma tag separada para perguntas exclusivas do C ++ 11. Muitos de nós, especialmente programadores em grandes empresas, ainda estamos presos a compiladores que ainda não suportam totalmente o C ++ 11. Eu atualizei a pergunta para ser precisa para os dois padrões.
Tim Meyer
26

Eu acho que você está se referindo ao problema em C (e C ++) que retornar um array de uma função não é permitido (ou pelo menos não funcionará como esperado) - isso ocorre porque o retorno do array irá (se você escrever em a forma simples) retorna um ponteiro para a matriz real na pilha, que então é imediatamente removida quando a função retorna.

Mas, neste caso, funciona, porque std::vectoré uma classe, e classes, como structs, podem (e serão) copiadas para o contexto do chamador. [Na verdade, a maioria dos compiladores otimizará esse tipo específico de cópia usando algo chamado "Otimização do valor de retorno", especificamente introduzida para evitar a cópia de objetos grandes quando eles são retornados de uma função, mas isso é uma otimização e, da perspectiva dos programadores, se comporte como se o construtor de atribuição fosse chamado para o objeto]

Contanto que você não retorne um ponteiro ou uma referência a algo que está dentro do retorno da função, tudo bem.

Mats Petersson
fonte
13

Para entender bem o comportamento, você pode executar este código:

#include <iostream>

class MyClass
{
  public:
    MyClass() { std::cout << "run constructor MyClass::MyClass()" << std::endl; }
    ~MyClass() { std::cout << "run destructor MyClass::~MyClass()" << std::endl; }
    MyClass(const MyClass& x) { std::cout << "run copy constructor MyClass::MyClass(const MyClass&)" << std::endl; }
    MyClass& operator = (const MyClass& x) { std::cout << "run assignation MyClass::operator=(const MyClass&)" << std::endl; }
};

MyClass my_function()
{
  std::cout << "run my_function()" << std::endl;
  MyClass a;
  std::cout << "my_function is going to return a..." << std::endl;
  return a;
}

int main(int argc, char** argv)
{
  MyClass b = my_function();

  MyClass c;
  c = my_function();

  return 0;
}

O resultado é o seguinte:

run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run constructor MyClass::MyClass()
run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run assignation MyClass::operator=(const MyClass&)
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()

Observe que este exemplo foi fornecido no contexto C ++ 03, ele poderia ser melhorado para C ++> = 11

Caduchon
fonte
1
Este exemplo seria mais completo se incluísse também um construtor de movimento e um operador de atribuição de movimento, e não apenas o construtor de cópia e o operador de atribuição de cópia. (Se as funções de movimentação não estiverem presentes, as de cópia serão usadas em seu lugar.)
Some Guy
@SomeGuy Eu concordo, mas não uso C ++ 11. Não posso fornecer conhecimentos que não tenho. Eu adiciono uma nota. Sinta-se à vontade para adicionar uma resposta para C ++> = 11. :-)
Caduchon
-5

Não concordo e não recomendo devolver vector:

vector <double> vectorial(vector <double> a, vector <double> b)
{
    vector <double> c{ a[1] * b[2] - b[1] * a[2], -a[0] * b[2] + b[0] * a[2], a[0] * b[1] - b[0] * a[1] };
    return c;
}

Isso é muito mais rápido:

void vectorial(vector <double> a, vector <double> b, vector <double> &c)
{
    c[0] = a[1] * b[2] - b[1] * a[2]; c[1] = -a[0] * b[2] + b[0] * a[2]; c[2] = a[0] * b[1] - b[0] * a[1];
}

Testei no Visual Studio 2017 com os seguintes resultados no modo de lançamento:

8,01 MOPs por referência
5,09 MOPs retornando vetor

No modo de depuração, as coisas são muito piores:

0,053 MOPS por referência
0,034 MOPs por vetor de retorno

mathengineer
fonte
-10

Na verdade, isso é uma falha de design. Você não deve usar um valor de retorno para nada que não seja um primitivo para nada que não seja relativamente trivial.

A solução ideal deve ser implementada através de um parâmetro de retorno com decisão sobre referência / ponteiro e o uso adequado de um "const \ 'y \' ness" como descritor.

Além disso, você deve perceber que o rótulo em uma matriz em C e C ++ é efetivamente um ponteiro e sua assinatura é efetivamente um deslocamento ou um símbolo de adição.

Portanto, o rótulo ou ptr array_ptr === rótulo do array, portanto, retornando foo [deslocamento] está realmente dizendo o elemento de retorno na localização do ponteiro de memória foo + deslocamento do tipo tipo de retorno.

Newbstarr
fonte
5
..........o que. Parece claro que você não está qualificado para lançar acusações como "falha de design". E, de fato, a promoção da semântica de valor por operações RVO e mover-se é um dos os principais sucesso es de estilo moderno C ++. Mas você parece estar preso a pensar em arrays e ponteiros brutos, então não espero que você perceba isso.
underscore_d