Tại sao unique_ptr <Derogen> ngầm định chuyển thành unique_ptr <Base>?


21

Tôi đã viết đoạn mã sau sử dụng unique_ptr<Derived>nơi unique_ptr<Base>dự kiến

class Base {
    int i;
 public:
    Base( int i ) : i(i) {}
    int getI() const { return i; }
};

class Derived : public Base {
    float f;
 public:
    Derived( int i, float f ) : Base(i), f(f) {}
    float getF() const { return f; }
};

void printBase( unique_ptr<Base> base )
{
    cout << "f: " << base->getI() << endl;
}

unique_ptr<Base> makeBase()
{
    return make_unique<Derived>( 2, 3.0f );
}

unique_ptr<Derived> makeDerived()
{
    return make_unique<Derived>( 2, 3.0f );
}

int main( int argc, char * argv [] )
{
    unique_ptr<Base> base1 = makeBase();
    unique_ptr<Base> base2 = makeDerived();
    printBase( make_unique<Derived>( 2, 3.0f ) );

    return 0;
}

và tôi dự kiến ​​mã này sẽ không biên dịch, bởi vì theo sự hiểu biết của tôi unique_ptr<Base>unique_ptr<Derived>là các loại không liên quan và unique_ptr<Derived>thực tế không có nguồn gốc từunique_ptr<Base> đó nên việc chuyển nhượng không nên hoạt động.

Nhưng nhờ một số phép thuật mà nó hoạt động, và tôi không hiểu tại sao, hoặc thậm chí nếu nó an toàn để làm như vậy. Ai đó có thể giải thích?


3
con trỏ thông minh là để làm phong phú những gì con trỏ có thể không làm hạn chế nó. Nếu điều này là không thể unique_ptrthì sẽ khá vô dụng khi có sự kế thừa
idclev 463035818

3
"Nhưng nhờ một số phép thuật, nó hoạt động" . Gần như, bạn có UB vì Basekhông có hàm hủy ảo.
Jarod42

Câu trả lời:


25

Một chút ma thuật mà bạn đang tìm kiếm là công cụ chuyển đổi số 6 ở đây :

template<class U, class E>
unique_ptr(unique_ptr<U, E> &&u) noexcept;

Nó cho phép xây dựng một std::unique_ptr<T>cách ngầm định từ hết hạnstd::unique_ptr<U> nếu (đánh bóng qua các thông số cho rõ ràng):

unique_ptr<U, E>::pointer hoàn toàn có thể chuyển đổi thành pointer

Có thể nói, nó bắt chước các chuyển đổi con trỏ thô, bao gồm các chuyển đổi xuất phát từ cơ sở và thực hiện những gì bạn mong đợi ™ một cách an toàn (về thời gian tồn tại - bạn vẫn cần đảm bảo rằng loại cơ sở có thể bị xóa đa hình).


2
AFAIK người thông minh Basesẽ không gọi kẻ hủy diệt Derived, vì vậy tôi không chắc liệu nó có thực sự an toàn hay không. (Nó không kém phần an toàn so với con trỏ thô, phải thừa nhận.)
cpplearner

14

Bởi vì std::unique_ptrcó một hàm tạo chuyển đổi như

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;

Hàm tạo này chỉ tham gia giải quyết quá tải nếu tất cả các điều sau là đúng:

a) unique_ptr<U, E>::pointerhoàn toàn có thể chuyển đổi thànhpointer

...

A Derived*có thể chuyển đổi thành Base*ngầm định, sau đó hàm tạo chuyển đổi có thể được áp dụng cho trường hợp này. Sau đó, a std::unique_ptr<Base>có thể được chuyển đổi từ một std::unique_ptr<Derived>con trỏ giống như con trỏ thô. (Lưu ý rằng std::unique_ptr<Derived>phải là một giá trị để xây dựng std::unique_ptr<Base>vì đặc tính của std::unique_ptr.)


7

Bạn có thể ngầm xây dựng một std::unique_ptr<T>ví dụ từ một rvalue của std::unique_ptr<S>bất cứ khi nào Scó thể chuyển đổi thành T. Điều này là do nhà xây dựng số 6 ở đây . Quyền sở hữu được chuyển giao trong trường hợp này.

Trong ví dụ của bạn, bạn chỉ có std::uinque_ptr<Derived>các giá trị loại (vì giá trị trả về std::make_uniquelà một giá trị ) và khi bạn sử dụng giá trị đó như một std::unique_ptr<Base>, hàm tạo được đề cập ở trên được gọi. Do đó, std::unique_ptr<Derived>các đối tượng trong câu hỏi chỉ sống trong một khoảng thời gian ngắn, tức là chúng được tạo ra, sau đó quyền sở hữu được chuyển cho std::unique_ptr<Base>đối tượng được sử dụng thêm.

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.