Como posso passar std :: unique_ptr para uma função

101

Como posso passar um std::unique_ptrem uma função? Digamos que eu tenha a seguinte aula:

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

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

O seguinte não 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 que não posso passar um std::unique_ptrpara uma função? Certamente este é o objetivo principal do construto? Ou o comitê de C ++ pretendia que eu voltasse às dicas de estilo C brutas e as passasse assim:

MyFunc(&(*ptr)); 

E o mais estranho de tudo, por que essa é uma maneira OK de passar isso? Parece terrivelmente inconsistente:

MyFunc(unique_ptr<A>(new A(1234)));
user3690202
fonte
7
Não há nada de errado em "voltar" para ponteiros brutos de estilo C, desde que sejam ponteiros brutos não proprietários. Você pode preferir escrever 'ptr.get ()'. Embora, se você não precisar de nulidade, uma referência seria preferível.
Chris Drew de

Respostas:

148

Existem basicamente duas opções aqui:

Passe o ponteiro inteligente por referência

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);
}

Mova o ponteiro inteligente para o argumento da função

Observe que, neste caso, a declaração será válida!

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
fonte
@VermillionAzure: O recurso ao qual você está se referindo normalmente é chamado de não copiável, não exclusivo.
Bill Lynch
1
Obrigado. Neste caso, eu queria criar uma instância, executar algumas ações nela e depois passar a propriedade para outra pessoa, para a qual move () parece perfeito.
user3690202
7
Você só deve passar um unique_ptrpor referência se a função puder ou não sair dele. E então deve ser uma referência de rvalue. Para observar um objeto sem exigir nada sobre sua semântica de propriedade, use uma referência como A const&ou A&.
Potatoswatter
14
Ouça a palestra de Herb Sutters na CppCon 2014. Ele desestimula veementemente a passagem de referências para unique_ptr <>. A essência é que você deve apenas usar ponteiros inteligentes como unique_ptr <> ou shared_ptr <> ao lidar com propriedade. Consulte www.youtube.com/watch?v=xnqTKD8uD64 Aqui você pode encontrar os slides github.com/CppCon/CppCon2014/tree/master/Presentations/…
schorsch_76
2
@Furihr: É apenas uma forma de mostrar qual é o valor de ptrapós a mudança.
Bill Lynch
28

Você está passando por valor, o que implica fazer uma cópia. Isso não seria muito original, seria?

Você pode mover o valor, mas isso implica passar a propriedade do objeto e o controle de sua vida útil para a função.

Se for garantido que o tempo de vida do objeto existirá durante o tempo de vida da chamada para MyFunc, basta passar um ponteiro bruto via ptr.get().

Mark Tolonen
fonte
17

Por que não posso passar um unique_ptrpara uma função?

Você não pode fazer isso porque unique_ptrtem um construtor de movimento, mas não um construtor de cópia. De acordo com o padrão, quando um construtor de movimento é definido, mas um construtor de cópia não é definido, o construtor de cópia é excluído.

12.8 Copiar e mover objetos de classe

...

7 Se a definição da classe não declara explicitamente um construtor de cópia, um é declarado implicitamente. Se a definição da classe declara um construtor de movimentação ou operador de atribuição de movimentação, o construtor de cópia declarado implicitamente é definido como excluído;

Você pode passar o unique_ptrpara a função usando:

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

e use-o como você fez:

ou

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

e usá-lo como:

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

Nota importante

Observe que se você usar o segundo método, ptrnão tem propriedade do ponteiro após a chamada para std::move(ptr)retorna.

void MyFunc(std::unique_ptr<A>&& arg)teria o mesmo efeito, void MyFunc(std::unique_ptr<A>& arg)já que ambos são referências.

No primeiro caso, ptrainda tem propriedade do ponteiro após a chamada para MyFunc.

R Sahu
fonte
5

Como MyFuncnão se apropria, seria melhor ter:

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

ou melhor

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

Se você realmente deseja assumir a propriedade, deverá mover seu recurso:

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

ou passe diretamente uma referência de valor r:

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

std::unique_ptr não possui cópia proposital para garantia de ter apenas um proprietário.

Jarod42
fonte
Esta resposta não tem a aparência da chamada para MyFunc nos seus dois primeiros casos. Não tem certeza se está usando ptr.get()ou apenas passando o ptrpara a função?
taylorstina
Qual é a assinatura pretendida MyFuncpara passar uma referência de valor r?
James Hirschorn
@JamesHirschorn: void MyFunc(A&& arg)leva a referência do valor r ...
Jarod42 de
@ Jarod42 Isso é o que eu adivinhei, mas com typedef int A[], MyFunc(std::make_unique<A>(N))dá o erro do compilador: erro: inicialização inválida de referência do tipo 'int (&&) []' da expressão do tipo 'std :: _ MakeUniq <int []> :: __ array' { aka 'std :: unique_ptr <int [], std :: default_delete <int []>>'} É g++ -std=gnu++11recente o suficiente?
James Hirschorn
@ Jarod42 Confirmei falha para C ++ 14 com ideone: ideone.com/YRRT24
James Hirschorn
4

Por que não posso passar um unique_ptrpara uma função?

Você pode, mas não por cópia - porque std::unique_ptr<>não é construtível por cópia.

Certamente este é o objetivo principal do construto?

Entre outras coisas, std::unique_ptr<>é projetado para marcar inequivocamente a propriedade única (em oposição a std::shared_ptr<>).

E o mais estranho de tudo, por que essa é uma maneira OK de passar isso?

Porque, nesse caso, não há construção de cópia.

Nielk
fonte
Obrigado pela sua resposta. Só por curiosidade, você pode explicar por que a última forma de passar não é usando o construtor de cópia? Eu teria pensado que o uso do construtor de unique_ptr geraria uma instância na pilha, que seria copiada para os argumentos de MyFunc () usando o construtor de cópia? Embora eu admita que minhas lembranças são um pouco vagas nessa área.
user3690202
2
Como é um rvalue, o construtor de movimento é chamado em seu lugar. Embora seu compilador certamente irá otimizar isso de qualquer maneira.
Nielk de
0

Uma vez que unique_ptré de propriedade única, se você quiser passar como argumento tente

MyFunc(move(ptr));

Mas depois disso, o estado de ptrem mainserá nullptr.

0x6773
fonte
4
"será indefinido" - não, será nulo ou unique_ptr será bastante inútil.
TC de
0

Passar std::unique_ptr<T>como valor para uma função não está funcionando porque, como vocês mencionaram, unique_ptrnão é copiável.

Que tal isso?

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

este código está funcionando

Riste
fonte
Se esse código funcionar, meu palpite é que ele não está copiando o unique_ptr, mas, na verdade, usando a semântica de movimento para movê-lo.
user3690202
pode ser a Otimização do valor de retorno (RVO), eu acho
Noueman Khalikine