¿Cómo puedo pasar std :: unique_ptr a una función?

97

¿Cómo puedo pasar una std::unique_ptra una función? Digamos que tengo la siguiente clase:

class A
{
public:
    A(int val)
    {
        _val = val;
    }

    int GetVal() { return _val; }
private:
    int _val;
};

Lo siguiente no se compila:

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);

    return 0;
}

¿Por qué no puedo pasar una std::unique_ptra una función? ¿Seguramente este es el propósito principal del constructo? ¿O el comité de C ++ tenía la intención de que recurriera a punteros de estilo C sin formato y lo pasara así:

MyFunc(&(*ptr)); 

Y lo más extraño de todo, ¿por qué es una buena forma de pasarlo? Parece horriblemente inconsistente:

MyFunc(unique_ptr<A>(new A(1234)));
user3690202
fuente
7
No hay nada de malo en "retroceder" a punteros en bruto de estilo C siempre que sean punteros en bruto que no sean propietarios. Es posible que prefiera escribir 'ptr.get ()'. Aunque, si no necesita nulidad, se preferiría una referencia.
Chris Drew

Respuestas:

142

Básicamente, hay dos opciones aquí:

Pase el puntero inteligente por referencia

void MyFunc(unique_ptr<A> & arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);
}

Mueva el puntero inteligente al argumento de la función

Tenga en cuenta que en este caso, la aserción se mantendrá.

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(move(ptr));
    assert(ptr == nullptr)
}
Bill Lynch
fuente
@VermillionAzure: la función a la que se refiere normalmente se denomina no copiable, no única.
Bill Lynch
1
Gracias. En este caso, quería crear una instancia, realizar algunas acciones en ella y luego pasar la propiedad a otra persona, para lo que move () parece perfecto.
user3690202
7
Solo debe pasar un unique_ptrpor referencia si la función puede o no moverse de él. Y luego debería ser una referencia rvalue. Para observar un objeto sin requerir nada sobre su semántica de propiedad, use una referencia como A const&o A&.
Potatoswatter
12
Escuche la charla de Herb Sutters en la CppCon 2014. Él desaconseja encarecidamente pasar referencias a unique_ptr <>. La esencia es que debe usar punteros inteligentes como unique_ptr <> o shared_ptr <> cuando se trata de la propiedad. Ver www.youtube.com/watch?v=xnqTKD8uD64 Aquí puede encontrar las diapositivas github.com/CppCon/CppCon2014/tree/master/Presentations/…
schorsch_76
2
@Furihr: Es solo una forma de mostrar cuál es el valor de ptrdespués de la mudanza.
Bill Lynch
26

Lo estás pasando por valor, lo que implica hacer una copia. Eso no sería muy especial, ¿verdad?

Puede mover el valor, pero eso implica pasar la propiedad del objeto y el control de su vida útil a la función.

Si se garantiza que la vida útil del objeto existe durante la vida útil de la llamada a MyFunc, simplemente pase un puntero sin procesar a través de ptr.get().

Mark Tolonen
fuente
17

¿Por qué no puedo pasar una unique_ptra una función?

No puede hacer eso porque unique_ptrtiene un constructor de movimiento pero no un constructor de copia. De acuerdo con el estándar, cuando se define un constructor de movimiento pero no se define un constructor de copia, se elimina el constructor de copia.

12.8 Copiar y mover objetos de clase

...

7 Si la definición de clase no declara explícitamente un constructor de copia, uno se declara implícitamente. Si la definición de clase declara un constructor de movimiento o un operador de asignación de movimiento, el constructor de copia declarado implícitamente se define como eliminado;

Puede pasar el unique_ptra la función usando:

void MyFunc(std::unique_ptr<A>& arg)
{
    cout << arg->GetVal() << endl;
}

y úsalo como lo has hecho:

o

void MyFunc(std::unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

y utilícelo como:

std::unique_ptr<A> ptr = std::unique_ptr<A>(new A(1234));
MyFunc(std::move(ptr));

Nota IMPORTANTE

Tenga en cuenta que si utiliza el segundo método, ptrno tiene la propiedad del puntero después de la llamada a std::move(ptr)devoluciones.

void MyFunc(std::unique_ptr<A>&& arg)tendría el mismo efecto que void MyFunc(std::unique_ptr<A>& arg)ya que ambos son referencias.

En el primer caso, ptrtodavía tiene la propiedad del puntero después de la llamada a MyFunc.

R Sahu
fuente
5

Como MyFuncno se apropia, sería mejor tener:

void MyFunc(const A* arg)
{
    assert(arg != nullptr); // or throw ?
    cout << arg->GetVal() << endl;
}

o mejor

void MyFunc(const A& arg)
{
    cout << arg.GetVal() << endl;
}

Si realmente desea tomar posesión, debe mover su recurso:

std::unique_ptr<A> ptr = std::make_unique<A>(1234);
MyFunc(std::move(ptr));

o pasar directamente una referencia de valor r:

MyFunc(std::make_unique<A>(1234));

std::unique_ptr no tiene copia a propósito para garantizar tener un solo dueño.

Jarod42
fuente
En esta respuesta falta el aspecto de la llamada a MyFunc para sus dos primeros casos. ¿No estás seguro de si estás usando ptr.get()o simplemente pasando el ptra la función?
Taylorstine
¿Cuál es la firma prevista de MyFuncpara pasar una referencia de valor r?
James Hirschorn
@JamesHirschorn: void MyFunc(A&& arg)toma la referencia de valor r ...
Jarod42
@ Jarod42 Eso es lo que supuse, pero con typedef int A[], MyFunc(std::make_unique<A>(N))da el error del compilador: error: inicialización no válida de la referencia del tipo 'int (&&) []' de la expresión del tipo 'std :: _ MakeUniq <int []> :: __ array' { también conocido como 'std :: unique_ptr <int [], std :: default_delete <int []>>'} ¿Es lo g++ -std=gnu++11suficientemente reciente?
James Hirschorn
@ Jarod42 Confirmé la falla de C ++ 14 con ideone: ideone.com/YRRT24
James Hirschorn
4

¿Por qué no puedo pasar una unique_ptra una función?

Puede hacerlo, pero no por copia, porque std::unique_ptr<>no se puede copiar.

¿Seguramente este es el propósito principal del constructo?

Entre otras cosas, std::unique_ptr<>está diseñado para marcar inequívocamente la propiedad única (a diferencia de std::shared_ptr<>).

Y lo más extraño de todo, ¿por qué es una buena forma de pasarlo?

Porque en ese caso, no hay construcción de copias.

Nielk
fuente
Gracias por tu respuesta. Solo por interés, ¿puede explicar por qué la última forma de pasarlo no es usar el constructor de copia? Habría pensado que el uso del constructor de unique_ptr generaría una instancia en la pila, que se copiaría en los argumentos de MyFunc () usando el constructor de copia. Aunque admito que mi recuerdo es un poco vago en esta área.
user3690202
2
Como es un rvalue, en su lugar se llama al constructor move. Aunque su compilador ciertamente optimizará esto de todos modos.
Nielk
0

Dado que unique_ptres para propiedad única, si desea pasarlo como argumento, intente

MyFunc(move(ptr));

Pero después de eso, el estado de ptrin mainserá nullptr.

0x6773
fuente
4
"será indefinido" - no, será nulo, o unique_ptrsería bastante inútil.
TC
0

Pasar std::unique_ptr<T>como valor a una función no funciona porque, como ustedes mencionan, unique_ptrno se puede copiar.

¿Qué pasa con esto?

std::unique_ptr<T> getSomething()
{
   auto ptr = std::make_unique<T>();
   return ptr;
}

este código está funcionando

Riste
fuente
Si ese código funciona, mi suposición sería que no está copiando el unique_ptr, sino que de hecho usa la semántica de movimiento para moverlo.
user3690202
podría ser la Optimización del valor de retorno (RVO), supongo
Noueman Khalikine