¿Por qué está bien devolver un 'vector' de una función?

108

Considere este código. He visto este tipo de código varias veces. wordses un vector local. ¿Cómo es posible devolverlo desde una función?

¿Podemos garantizar que no morirá?

 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
fuente
18
Se copia cuando regresa.
songyuanyao
6
No hay garantías de uno .. Se van a morir, pero después de que ha copiado.
Maroun
7
Solo tiene un problema si su función devuelve una referencia:std::vector<std::string>&
Caduchon
14
@songyuanyao no, se moverá.
Derecha
15
@songyuanyao Sí. C ++ 11 es el estándar actual, por lo que C ++ 11 es C ++.
pliegue derecho

Respuestas:

68

¿Podemos garantizar que no morirá?

Siempre que no se devuelva ninguna referencia, está perfectamente bien hacerlo. wordsse moverá a la variable que recibe el resultado.

La variable local quedará fuera de alcance. después de que fue movido (o copiado).

πάντα ῥεῖ
fuente
2
Pero, ¿es eficiente o tiene algún problema de rendimiento, por ejemplo, para el vector que puede contener 1000 entradas?
zar
@zadane ¿Estaba esto en cuestión? También mencioné un movimiento que evitará tomar una copia del valor de retorno en realidad (disponible al menos con el estándar actual).
πάντα ῥεῖ
2
No, no realmente en la pregunta, pero estaba buscando una respuesta desde esa perspectiva de forma independiente. No sé si publico mi pregunta, me temo que la marcarán como un duplicado de esto :)
zar
@zadane "Me temo que lo marcarán como un duplicado de esto" Bien podría ser. Solo eche un vistazo a la respuesta más votada . Incluso para las implementaciones más antiguas, no debe preocuparse, ya que esos compiladores las optimizarán en su mayoría correctamente.
πάντα ῥεῖ
107

Pre C ++ 11:

La función no devolverá la variable local, sino una copia de ella. Sin embargo, su compilador podría realizar una optimización donde no se realiza ninguna acción de copia real.

Consulte esta pregunta y respuesta para obtener más detalles.

C ++ 11:

La función moverá el valor. Consulte esta respuesta para obtener más detalles.

Tim Meyer
fuente
2
Se moverá, no se copiará. Esto esta garantizado.
derechazo
1
¿Esto también se aplica a C ++ 10?
Tim Meyer
28
No existe C ++ 10.
Derecha
C ++ 03 no tenía semántica de movimiento (aunque es posible que se haya eliminado la copia), pero C ++ es C ++ 11 y la pregunta era sobre C ++.
Derecha
19
Hay una etiqueta separada para preguntas exclusivas de C ++ 11. Muchos de nosotros, especialmente los programadores de empresas más grandes, todavía estamos atrapados en compiladores que aún no son totalmente compatibles con C ++ 11. Actualicé la pregunta para que sea precisa para ambos estándares.
Tim Meyer
26

Creo que se está refiriendo al problema en C (y C ++) de que no se permite devolver una matriz desde una función (o al menos no funcionará como se esperaba); esto se debe a que la matriz devolverá (si la escribe en la forma simple) devuelve un puntero a la matriz real en la pila, que luego se elimina rápidamente cuando regresa la función.

Pero en este caso, funciona, porque std::vectores una clase, y las clases, como las estructuras, pueden (y serán) copiadas al contexto de los llamadores. [En realidad, la mayoría de los compiladores optimizarán este tipo particular de copia usando algo llamado "Optimización del valor de retorno", específicamente introducido para evitar copiar objetos grandes cuando son devueltos desde una función, pero eso es una optimización, y desde la perspectiva de los programadores, lo hará comportarse como si el constructor de asignaciones fuera llamado para el objeto]

Siempre que no devuelva un puntero o una referencia a algo que está dentro de la función que regresa, está bien.

Mats Petersson
fuente
13

Para comprender bien el comportamiento, puede ejecutar 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;
}

El resultado es el siguiente:

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()

Tenga en cuenta que este ejemplo se proporcionó en el contexto de C ++ 03, podría mejorarse para C ++> = 11

Caduchon
fuente
1
Este ejemplo sería más completo si incluyera también un constructor de movimiento y un operador de asignación de movimiento, y no solo un constructor de copia y un operador de asignación de copia. (Si las funciones de movimiento no están presentes, se usarán las de copia en su lugar.)
Some Guy
@SomeGuy Estoy de acuerdo, pero no uso C ++ 11. No puedo proporcionar conocimientos que no tengo. Añado una nota. No dude en agregar una respuesta para C ++> = 11. :-)
Caduchon
-5

No estoy de acuerdo y no recomiendo devolver un 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;
}

Esto es mucho más 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];
}

Probé en Visual Studio 2017 con los siguientes resultados en modo de lanzamiento:

8.01 MOP por referencia
5.09 MOP que regresa vector

En el modo de depuración, las cosas son mucho peores:

0,053 MOPS por referencia
0,034 MOP por vector de retorno

ingeniero matemático
fuente
-10

En realidad, esto es un error de diseño. No debería utilizar un valor de retorno para nada que no sea un primitivo para algo que no sea relativamente trivial.

La solución ideal debe implementarse mediante un parámetro de retorno con una decisión sobre la referencia / puntero y el uso adecuado de una "const \ 'y \' ness" como descriptor.

Además de esto, debe darse cuenta de que la etiqueta en una matriz en C y C ++ es efectivamente un puntero y su suscripción es efectivamente un desplazamiento o un símbolo de adición.

Así que la etiqueta o ptr array_ptr === array label que devuelve foo [offset] en realidad dice que el elemento de retorno en la ubicación del puntero de memoria foo + offset del tipo tipo de retorno.

Newbstarr
fuente
5
..........qué. Parece claro que no estás calificado para lanzar acusaciones como "falla de diseño". Y de hecho, la promoción de la semántica de valor por parte de RVO y las operaciones de movimiento es uno de los principales éxitos del estilo C ++ moderno. Pero parece que está atascado pensando en matrices y punteros sin procesar, por lo que no esperaría que lo entendiera.
underscore_d