2016-09-13 28 views
6

Tôi có một lớp cơ sở và lớp con của nó:Quay trở lại std :: make_unique <SubClass> hoạt động như thế nào?

class Base { 
    public: 
    virtual void hi() { 
     cout << "hi" << endl; 
    } 
}; 

class Derived : public Base { 
    public: 
    void hi() override { 
     cout << "derived hi" << endl; 
    } 
}; 

Đang cố gắng để tạo ra một hàm mà tạo ra một con trỏ duy nhất của một đối tượng nguồn gốc.

1) Cái này hoạt động:

std::unique_ptr<Base> GetDerived() { 
    return std::make_unique<Derived>(); 
} 

2) Nhưng, cái này thất bại trong việc biên dịch:

std::unique_ptr<Base> GetDerived2() { 
    auto a = std::make_unique<Derived>(); 
    return a; 
} 

3) std :: thái hoạt động:

std::unique_ptr<Base> GetDerived3() { 
    auto a = std::make_unique<Derived>(); 
    return std::move(a); 
} 

4) Nếu tôi tạo một phiên bản Cơ sở, cả hai đều hoạt động:

std::unique_ptr<Base> GetDerived4() { 
    auto a = std::make_unique<Base>(); 
    return a; 
} 

std::unique_ptr<Base> GetDerived5() { 
    auto a = std::make_unique<Base>(); 
    return std::move(a); 
} 

Tại sao (2) không thành công nhưng những người khác làm việc?

Trả lời

7

std::unique_ptr không thể sao chép được, chỉ có thể di chuyển được. Lý do bạn có thể return std::make_unique<Derived> từ một hàm được khai báo trả về std::unique_ptr<Base> là có một chuyển đổi từ một đến cái kia.

Vì vậy, 1) tương đương với:

std::unique_ptr<Base> GetDerived() { 
    return std::unique_ptr<Base>(std::made_unique<Derived>()); 
} 

Kể từ khi giá trị trả về từ std::make_unique là một rvalue, giá trị trả về là di chuyển-xây dựng.

Contrast đó để 2), tương đương với:

std::unique_ptr<Base> GetDerived2() { 
    std::unique_ptr<Derived> a = std::make_unique<Derived>(); 
    return std::unique_ptr<Base>(a); 
} 

từ a là một giá trị trái, giá trị trả về phải được sao chép-xây dựng, và std::unique_ptr là phi copyable.

3) hoạt động vì bạn truyền giá trị hoa hồng a thành giá trị và giá trị trả về có thể được di chuyển.

4) và 5) hoạt động vì bạn đã có std::unique_ptr<Base> và không cần phải tạo một cái để trả lại.

0

Trong ví dụ được liệt kê ở trên, (1) trả về một giá trị nhưng (2) không phải là một giá trị và đang cố gắng sao chép trên unique_ptr mà bạn không thể thực hiện cho unique_ptr.

Sử dụng di chuyển hoạt động vì bạn đang xử lý unique_ptr tại thời điểm đó làm giá trị.

1

std::unique_ptr<> không có constructor sao chép, nhưng nó có một constructor chuyển từ một con trỏ có liên quan, ví dụ:

unique_ptr(unique_ptr&& u);   // move ctor 
template< class U, class E > 
unique_ptr(unique_ptr<U, E>&& u); // move ctor from related unique_ptr 

Các constructor thứ hai đòi hỏi điều kiện nhất định (xem here). Vậy tại sao mã 2 của bạn không hoạt động, nhưng 4 đã làm? Trong 4, bạn không sử dụng bất kỳ hàm tạo nào, vì kiểu trả về giống hệt đối tượng, chính đối tượng đó đã được trả về. Trong 2 mặt khác, kiểu trả về là khác nhau và cần gọi hàm tạo, nhưng yêu cầu đó là std::move().

1

Trong mọi trường hợp nhưng (2) giá trị trả lại được coi là (một số loại) giá trị.Trong (2) nó không phải là, bởi vì các loại không phù hợp với di chuyển tiềm ẩn đã bị chặn.

Trong lần lặp lại tiêu chuẩn sau, (2) cũng sẽ chuyển hoàn toàn.

Tất cả trong số họ sớm tham gia vào hành vi không xác định sau khi được gọi, vì họ cố xóa đối tượng Derived qua con trỏ tới Base. Để khắc phục điều này, hãy ghi lại chức năng deleter.

template<class T> 
using smart_unique=std::unique_ptr<T, void(*)(void*)>; 

template<class T, class...Args> 
smart_unique<T> make_smart_unique(Args&&... args){ 
    return { 
    new T(std::forward<Args>(args)...), 
    [](void*ptr){ delete static_cast<T*>(ptr); } 
    }; 
} 
template<class T> 
static const smart_unique<T> empty_smart_unique{ nullptr, [](void*){} }; 

Đây là những con trỏ duy nhất đủ thông minh để xử lý đa hình như shared_ptr có thể.

+1

Nếu bạn cho 'Cơ sở' một trình phá hủy ảo, bạn không cần trình gỡ rối tùy chỉnh nữa. Mặt khác, –

+0

@RemyLebeau giới thiệu một vtable mà trước đây không cần thiết, tùy thuộc vào ứng dụng, người lập trình có thể thích kích thước đối tượng nhỏ hơn không có vnable –

+1

@MM: 'Base' và' Derived' đã có vtables do với phương thức 'hi()' ảo. Bất cứ lúc nào bạn đối phó với đa hình (chỉ hoạt động với con trỏ/tham chiếu), các lớp cơ sở phải luôn có các destructor ảo để các đối tượng có nguồn gốc có thể bị phá hủy đúng cách thông qua các con trỏ cơ bản. –

Các vấn đề liên quan