2012-09-13 26 views
17

Tôi đang tạo một lớp liên kết với một số mã API Windows, bây giờ một trong các con trỏ tôi phải khởi tạo được thực hiện bằng cách gọi một hàm gốc khởi tạo nó.Khởi tạo một std :: unique_ptr bằng cách chuyển địa chỉ của con trỏ

Con trỏ của tôi là loại std::unique_ptr với một dấu tùy chỉnh, gọi hàm WinAPI deleter được cung cấp, tuy nhiên tôi không thể chuyển unique_ptr với toán tử địa chỉ & cho hàm init. Tại sao?

Tôi đã tạo ra một mẫu thể hiện vấn đề của tôi:

#include <memory> 

struct foo 
{ 
    int x; 
}; 

struct custom_deleter {}; 

void init_foo(foo** init) 
{ 
    *init = new foo(); 
} 

int main() 
{ 
    std::unique_ptr<foo, custom_deleter> foo_ptr; 

    init_foo(&foo_ptr); 
} 

Tiếng sủa biên dịch và nói:

source.cpp: In function 'int main()': 
source.cpp:19:21: error: cannot convert 'std::unique_ptr<foo, custom_deleter>*' to 'foo**' for argument '1' to 'void init_foo(foo**)' 

Trả lời

18

Một nơi nào đó dưới tấm chăn, unique_ptr<foo> có thành viên dữ liệu kiểu foo*.

Tuy nhiên, người dùng của lớp không sửa đổi trực tiếp thành viên dữ liệu đó là không hợp lệ. Làm như vậy sẽ không nhất thiết phải bảo tồn các bất biến lớp của unique_ptr, đặc biệt nó sẽ không giải phóng giá trị con trỏ cũ (nếu có). Trong trường hợp đặc biệt của bạn, bạn không cần điều đó xảy ra, bởi vì giá trị trước đó là 0, nhưng nói chung nó sẽ xảy ra.

Vì lý do đó unique_ptr không cung cấp quyền truy cập vào thành viên dữ liệu, chỉ cho một bản sao giá trị của nó (qua get()operator->). Bạn không thể nhận được số foo** trong số unique_ptr của mình.

Bạn thay vì có thể viết:

foo *tmp; 
init_foo(&tmp); 
std::unique_ptr<foo, custom_deleter> foo_ptr(tmp); 

Đây là ngoại lệ an toàn vì lý do tương tự mà std::unique_ptr<foo, custom_deleter> foo_ptr(new foo()); là ngoại lệ an toàn: unique_ptr đảm bảo rằng bất cứ điều gì bạn vượt qua trong phương thức khởi dựng của nó cuối cùng sẽ được xóa bằng cách sử dụng deleter.

Btw, không custom_deleter cần operator()(foo*)? Hay tôi đã bỏ lỡ điều gì đó?

+0

Vì vậy, giải pháp là 'shared_ptr' hoặc quay trở lại con trỏ nguyên? –

+0

Và làm thế nào về nhận được? – ForEveR

+0

@ForEverR: điều gì về nó? Nó trả về giá trị * của thành viên dữ liệu đó, không phải là con trỏ tới thành viên dữ liệu đó. –

5

Steve đã giải thích vấn đề kỹ thuật là gì, tuy nhiên, vấn đề cơ bản sẽ đi sâu hơn nhiều: Mã sử ​​dụng thành ngữ hữu ích khi bạn xử lý các con trỏ trần. Tại sao mã này làm hai bước khởi tạo (đầu tiên tạo đối tượng, sau đó khởi tạo nó) ở nơi đầu tiên? Kể từ khi bạn muốn sử dụng con trỏ thông minh, tôi muốn đề nghị bạn một cách cẩn thận sửa lại mã:

foo* init_foo() 
{ 
    return new foo(); 
} 

int main() 
{ 
    std::unique_ptr<foo, custom_deleter> foo_ptr(init_foo()); 

} 

Tất nhiên, đổi tên init_foo()-create_foo() và có nó trả về một std::unique_ptr<foo> trực tiếp sẽ tốt hơn. Ngoài ra, khi bạn sử dụng hai bước khởi tạo, nó thường được khuyến khích để xem xét sử dụng một lớp học để bọc dữ liệu.

+0

thấy rằng 'init_foo' của tôi là một cuộc gọi API cửa sổ, tôi không thể thay đổi nó. Vì vậy, bạn đề nghị tôi viết một wrapper xung quanh chức năng đó? Tại sao? An toàn ngoại lệ không phải là vấn đề đối với mỗi @SteveJessop. –

+2

@Tony: mã sbi chỉ "sạch hơn", dòng duy nhất trong 'main' làm những gì nó nói. Mã của tôi cần một biến tạm thời để tạo ra thứ bạn thực sự muốn. Điều đó không thích hợp hơn, mã của tôi chỉ là những gì bạn phải làm để gọi 'init_foo' như trước đây. –

+0

@SteveJessop: "... và có nó trả về một' std :: unique_ptr 'trực tiếp sẽ tốt hơn." – sbi

0

Bạn có thể sử dụng các thủ thuật sau đây:

template<class T> 
class ptr_setter 
{ 
public: 
    ptr_setter(T& Ptr): m_Ptr{Ptr} {} 
    ~ptr_setter() { m_Ptr.reset(m_RawPtr); } 

    ptr_setter(const ptr_setter&) = delete; 
    ptr_setter& operator=(const ptr_setter&) = delete; 

    auto operator&() { return &m_RawPtr; } 

private: 
    T& m_Ptr; 
    typename T::pointer m_RawPtr{}; 
}; 


// Macro will not be needed with C++17 class template deduction. 
// If you dislike macros (as all normal people should) 
// it's possible to replace it with a helper function, 
// although this would make the code a little more complex. 

#define ptr_setter(ptr) ptr_setter<decltype(ptr)>(ptr) 

và sau đó:

std::unique_ptr<foo, custom_deleter> foo_ptr; 
init_foo(&ptr_setter(foo_ptr)); 
Các vấn đề liên quan