Làm cách nào để chuyển std :: unique_ptr vào một hàm


97

Làm cách nào để chuyển std::unique_ptrmột hàm vào một hàm? Giả sử tôi có lớp sau:

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

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

Sau đây không biên dịch:

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

Tại sao tôi không thể truyền a std::unique_ptrvào một hàm? Chắc chắn đây là mục đích chính của cấu trúc? Hay ủy ban C ++ có ý định cho tôi quay lại con trỏ kiểu C thô và chuyển nó như thế này:

MyFunc(&(*ptr)); 

Và kỳ lạ nhất, tại sao đây lại là một cách tốt để vượt qua nó? Nó có vẻ không nhất quán kinh khủng:

MyFunc(unique_ptr<A>(new A(1234)));

7
Không có gì sai khi "quay lại" con trỏ kiểu C miễn là chúng không phải là con trỏ thô không sở hữu. Bạn có thể thích viết 'ptr.get ()'. Mặc dù, nếu bạn không cần khả năng vô hiệu, một tham chiếu sẽ được ưu tiên hơn.
Chris Drew

Câu trả lời:


143

Về cơ bản có hai tùy chọn ở đây:

Chuyển con trỏ thông minh bằng cách tham chiếu

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

Di chuyển con trỏ thông minh vào đối số hàm

Lưu ý rằng trong trường hợp này, khẳng định sẽ giữ nguyên!

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

@VermillionAzure: Tính năng bạn đang đề cập đến thường được gọi là không thể sao chép, không phải là duy nhất.
Bill Lynch

1
Cảm ơn. Trong trường hợp này, tôi muốn tạo một phiên bản, thực hiện một số hành động trên nó và sau đó chuyển quyền sở hữu cho người khác, mà move () có vẻ hoàn hảo.
dùng3690202

7
Bạn chỉ nên chuyển một unique_ptrtham chiếu nếu hàm có thể hoặc có thể không di chuyển khỏi nó. Và sau đó nó phải là một tham chiếu rvalue. Để quan sát một đối tượng mà không yêu cầu bất kỳ điều gì về ngữ nghĩa quyền sở hữu của nó, hãy sử dụng một tham chiếu như A const&hoặc A&.
Potatoswatter

12
Hãy nghe Herb Sutters nói chuyện trên CppCon 2014. Anh ấy thực sự không khuyến khích, chuyển tham chiếu đến unique_ptr <>. Bản chất là bạn chỉ nên sử dụng các con trỏ thông minh như unique_ptr <> hoặc shared_ptr <> khi bạn xử lý quyền sở hữu. Xem www.youtube.com/watch?v=xnqTKD8uD64 Tại đây, bạn có thể tìm thấy các trang trình bày github.com/CppCon/CppCon2014/tree/master/Presentations/…
schorsch_76

2
@Furihr: Đó chỉ là một cách để hiển thị giá trị của giá trị ptrsau khi di chuyển.
Bill Lynch

26

Bạn đang chuyển nó theo giá trị, nghĩa là tạo một bản sao. Nó sẽ không phải là rất độc đáo, phải không?

Bạn có thể di chuyển giá trị, nhưng điều đó có nghĩa là chuyển quyền sở hữu đối tượng và quyền kiểm soát thời gian tồn tại của nó cho hàm.

Nếu thời gian tồn tại của đối tượng được đảm bảo tồn tại trong suốt thời gian gọi đến MyFunc, chỉ cần chuyển một con trỏ thô qua ptr.get().


17

Tại sao tôi không thể truyền a unique_ptrvào một hàm?

Bạn không thể làm điều đó vì unique_ptrcó một phương thức khởi tạo di chuyển nhưng không có một phương thức khởi tạo sao chép. Theo tiêu chuẩn, khi một phương thức khởi tạo di chuyển được xác định nhưng một phương thức khởi tạo sao chép không được xác định, thì phương thức tạo bản sao sẽ bị xóa.

12.8 Sao chép và di chuyển các đối tượng lớp

...

7 Nếu định nghĩa lớp không khai báo rõ ràng một phương thức khởi tạo sao chép, thì một phương thức được khai báo ngầm định. Nếu định nghĩa lớp khai báo một phương thức khởi tạo di chuyển hoặc toán tử gán di chuyển, phương thức khởi tạo sao chép được khai báo ngầm định được định nghĩa là đã xóa;

Bạn có thể chuyển hàm unique_ptrcho hàm bằng cách sử dụng:

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

và sử dụng nó như bạn có:

hoặc là

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

và sử dụng nó như:

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

Lưu ý quan trọng

Xin lưu ý rằng nếu bạn sử dụng phương pháp thứ hai, ptrkhông có quyền sở hữu con trỏ sau khi gọi std::move(ptr)trả về.

void MyFunc(std::unique_ptr<A>&& arg)sẽ có cùng tác dụng void MyFunc(std::unique_ptr<A>& arg)vì cả hai đều là tham chiếu.

Trong trường hợp đầu tiên, ptrvẫn có quyền sở hữu con trỏ sau cuộc gọi tới MyFunc.


5

MyFunckhông có quyền sở hữu, sẽ tốt hơn nếu có:

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

hoặc tốt hơn

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

Nếu bạn thực sự muốn có quyền sở hữu, bạn phải di chuyển tài nguyên của mình:

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

hoặc chuyển trực tiếp tham chiếu giá trị r:

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

std::unique_ptr không cố ý sao chép để đảm bảo chỉ có một chủ sở hữu.


Câu trả lời này bị thiếu cuộc gọi đến MyFunc trông như thế nào đối với hai trường hợp đầu tiên của bạn. Bạn không chắc mình đang sử dụng ptr.get()hay chỉ truyền ptrvào hàm?
taylorstine

Chữ ký dự định MyFuncđể truyền tham chiếu giá trị r là gì?
James Hirschorn

@JamesHirschorn: void MyFunc(A&& arg)lấy tham chiếu giá trị r ...
Jarod42

@ Jarod42 Đó là những gì tôi đoán, nhưng với typedef int A[], gây MyFunc(std::make_unique<A>(N))ra lỗi trình biên dịch: error: khởi tạo không hợp lệ của tham chiếu kiểu 'int (&&) []' từ biểu thức kiểu 'std :: _ MakeUniq <int []> :: __ array' { aka 'std :: unique_ptr <int [], std :: default_delete <int []>>'} Đã g++ -std=gnu++11đủ gần đây chưa?
James Hirschorn

@ Jarod42 Tôi đã xác nhận thất bại cho C ++ 14 với Ideone: ideone.com/YRRT24
James Hirschorn

4

Tại sao tôi không thể truyền a unique_ptrvào một hàm?

Bạn có thể, nhưng không phải bằng cách sao chép - bởi vì std::unique_ptr<>không phải là bản sao có thể xây dựng.

Chắc chắn đây là mục đích chính của cấu trúc?

Trong số những thứ khác, std::unique_ptr<>được thiết kế để đánh dấu rõ ràng quyền sở hữu duy nhất (trái ngược với std::shared_ptr<>).

Và kỳ lạ nhất, tại sao đây lại là một cách tốt để vượt qua nó?

Bởi vì trong trường hợp đó, không có bản sao-xây dựng.


Cảm ơn câu trả lời của bạn. Thật thú vị, bạn có thể giải thích tại sao cách cuối cùng để truyền nó không sử dụng hàm tạo bản sao không? Tôi đã nghĩ rằng việc sử dụng phương thức khởi tạo của unique_ptr sẽ tạo ra một thể hiện trên ngăn xếp, thể hiện này sẽ được sao chép vào các đối số cho MyFunc () bằng cách sử dụng hàm tạo bản sao? Mặc dù tôi thừa nhận rằng hồi ức của tôi hơi mơ hồ về lĩnh vực này.
dùng3690202

2
Vì nó là một rvalue, thay vào đó, hàm tạo di chuyển được gọi. Mặc dù trình biên dịch của bạn chắc chắn sẽ tối ưu hóa điều này.
Nielk

0

unique_ptrlà quyền sở hữu duy nhất, nếu bạn muốn chuyển nó dưới dạng đối số, hãy thử

MyFunc(move(ptr));

Nhưng sau đó trạng thái ptrtrong mainsẽ được nullptr.


4
"will be undefined" - không, nó sẽ là rỗng, hoặc unique_ptrthà vô dụng.
TC

0

Truyền std::unique_ptr<T>dưới dạng giá trị cho một hàm không hoạt động vì, như các bạn đã đề cập, unique_ptrkhông thể sao chép được.

Cái này thì sao?

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

mã này đang hoạt động


Nếu mã đó hoạt động thì dự đoán của tôi sẽ là nó không sao chép unique_ptr, mà trên thực tế sử dụng ngữ nghĩa di chuyển để di chuyển nó.
user3690202

Tôi đoán có thể là Tối ưu hóa Giá trị Lợi nhuận (RVO)
Noueman Khalikine
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.