Chuyển shared_ptr <Derived> as shared_ptr <Base>


93

Phương pháp tốt nhất để chuyển một shared_ptrkiểu dẫn xuất sang một hàm nhận shared_ptrkiểu cơ sở là gì?

Tôi thường chuyển shared_ptrs bằng cách tham khảo để tránh một bản sao không cần thiết:

int foo(const shared_ptr<bar>& ptr);

nhưng điều này không hiệu quả nếu tôi cố gắng làm điều gì đó như

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

tôi có thể dùng

foo(dynamic_pointer_cast<Base, Derived>(bar));

nhưng điều này có vẻ không tối ưu vì hai lý do:

  • A dynamic_castcó vẻ hơi thừa đối với một dàn diễn viên đơn giản có nguồn gốc từ cơ sở.
  • Theo tôi hiểu, hãy dynamic_pointer_casttạo một bản sao (mặc dù là bản sao tạm thời) của con trỏ để chuyển tới hàm.

Có giải pháp nào tốt hơn không?

Cập nhật cho hậu thế:

Hóa ra là vấn đề thiếu tệp tiêu đề. Ngoài ra, những gì tôi đang cố gắng làm ở đây được coi là một phản vật chất. Nói chung là,

  • Chức năng mà không ảnh hưởng đến tuổi thọ của một đối tượng (tức là đối tượng vẫn có giá trị trong suốt thời gian của hàm) nên phải mất một tài liệu tham khảo đồng bằng hoặc con trỏ, ví dụ int foo(bar& b).

  • Các hàm sử dụng một đối tượng (tức là những người dùng cuối cùng của một đối tượng nhất định) phải nhận unique_ptrtheo giá trị, ví dụ int foo(unique_ptr<bar> b). Người gọi nên std::movenhập giá trị vào hàm.

  • Các hàm kéo dài thời gian tồn tại của một đối tượng phải nhận shared_ptrtheo giá trị, ví dụ int foo(shared_ptr<bar> b). Lời khuyên thông thường để tránh các tham chiếu vòng tròn được áp dụng.

Xem bài nói chuyện Back to Basics của Herb Sutter để biết thêm chi tiết.


8
Tại sao bạn muốn vượt qua a shared_ptr? Tại sao không có tham chiếu const của thanh?
ipc

2
Bất kỳ dynamicdiễn viên nào cũng chỉ cần thiết cho dự báo xuống. Ngoài ra, việc chuyển con trỏ dẫn xuất sẽ hoạt động tốt. Nó sẽ tạo một mới shared_ptrvới cùng một số tiền hoàn lại (và tăng nó lên) và một con trỏ đến cơ sở, sau đó liên kết với tham chiếu const. Tuy nhiên, vì bạn đã tham khảo, tôi không hiểu tại sao bạn lại muốn tham khảo shared_ptr. Nhận Base const&và gọi foo(*bar).
Xeo

@Xeo: Việc chuyển con trỏ dẫn xuất (tức là foo(bar)) không hoạt động, ít nhất là trong MSVC 2010.
Matt Kline

1
Ý bạn là gì khi nói "rõ ràng là không hoạt động"? Mã biên dịch và hoạt động chính xác; bạn đang hỏi làm thế nào để tránh việc tạo tạm thời shared_ptrđể truyền vào hàm? Tôi khá chắc rằng không có cách nào để tránh điều đó.
Mike Seymour

1
@Seth: Tôi không đồng ý. Tôi nghĩ rằng có lý do để chuyển một con trỏ được chia sẻ theo giá trị và có rất ít lý do để chuyển một con trỏ được chia sẻ bằng cách tham chiếu (và tất cả những điều này mà không ủng hộ các bản sao không cần thiết). Lý do tại đây stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

Câu trả lời:


47

Mặc dù BaseDerivedlà hiệp biến và con trỏ nguyên đối với họ sẽ hành động phù hợp, shared_ptr<Base>shared_ptr<Derived>không hiệp biến. Đây dynamic_pointer_castlà cách chính xác và đơn giản nhất để xử lý vấn đề này.

( Chỉnh sửa: static_pointer_cast sẽ thích hợp hơn vì bạn đang truyền từ nguồn gốc đến cơ sở, điều này an toàn và không yêu cầu kiểm tra thời gian chạy. Xem nhận xét bên dưới.)

Tuy nhiên, nếu foo()hàm của bạn không muốn tham gia vào việc kéo dài thời gian tồn tại (hay đúng hơn là tham gia vào quyền sở hữu chung của đối tượng), thì tốt nhất bạn nên chấp nhận một const Base&và bỏ shared_ptrqua khi chuyển nó đến foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

Ngoài ra, bởi vì shared_ptrcác loại không thể đồng biến, các quy tắc chuyển đổi ngầm định giữa các loại trả về đồng biến không áp dụng khi trả về các loại shared_ptr<T>.


39
Chúng không phải là đồng biến, nhưng shared_ptr<Derived>hoàn toàn có thể chuyển đổi thành shared_ptr<Base>, vì vậy mã sẽ hoạt động mà không có trò tai quái nào.
Mike Seymour

9
Um, shared_ptr<Ty>có một hàm tạo nhận a shared_ptr<Other>và thực hiện chuyển đổi thích hợp nếu Ty*hoàn toàn có thể chuyển đổi thành Other*. Và nếu cần một dàn cast, static_pointer_castthì ở đây có phải là thích hợp không dynamic_pointer_cast.
Pete Becker

Đúng, nhưng không phải với tham số tham chiếu của anh ta, như trong câu hỏi. Anh ta sẽ cần phải làm một bản sao, bất kể. Tuy nhiên, nếu anh ấy sử dụng refs để shared_ptrtránh số lượng tham chiếu, thì thực sự không có lý do chính đáng để sử dụng một shared_ptrngay từ đầu. Tốt nhất nên sử dụng const Base&thay thế.
Bret Kuhns

@PeteBecker Xem nhận xét của tôi với Mike về hàm tạo chuyển đổi. Tôi thực sự không biết về static_pointer_cast, cảm ơn.
Bret Kuhns

1
@TanveerBadar Không chắc chắn. Có lẽ điều này không thành công để biên dịch vào năm 2012? (đặc biệt là sử dụng Visual Studio 2010 hoặc 2012). Nhưng bạn hoàn toàn đúng, mã của OP hoàn toàn nên được biên dịch nếu định nghĩa đầy đủ của một lớp / có nguồn gốc công khai / được hiển thị cho trình biên dịch.
Bret Kuhns

32

Điều này cũng sẽ xảy ra nếu bạn quên chỉ định kế thừa công khai trên lớp dẫn xuất, tức là nếu giống như tôi, bạn viết thế này:

class Derived : Base
{
};

classlà cho các tham số mẫu; structlà để xác định các lớp. (Đây là nhiều nhất là 45% một trò đùa.)
Davis Herring

Đây chắc chắn nên được coi là giải pháp, không cần diễn viên vì chỉ thiếu công.
Alexis Paques

12

Có vẻ như bạn đang cố gắng quá nhiều. shared_ptrrẻ để sao chép; đó là một trong những mục tiêu của nó. Chuyển chúng đi xung quanh bằng cách tham khảo không thực sự đạt được nhiều thành tựu. Nếu bạn không muốn chia sẻ, hãy chuyển con trỏ thô.

Điều đó nói rằng, có hai cách để làm điều này mà tôi có thể nghĩ ra ngay từ đầu:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));

9
Không, chúng không rẻ để sao chép, chúng nên được chuyển qua tham chiếu bất cứ khi nào có thể.
Seth Carnegie

6
@SethCarnegie - Herb đã lập hồ sơ mã của bạn để xem liệu việc chuyển theo giá trị có phải là một điểm nghẽn không?
Pete Becker

25
@SethCarnegie - điều đó không trả lời câu hỏi tôi đã hỏi. Và, đối với những gì nó đáng giá, tôi đã viết bản shared_ptrtriển khai mà Microsoft vận chuyển.
Pete Becker

6
@SethCarnegie - bạn đã có kinh nghiệm ngược. Các tối ưu hóa bằng tay thường không nên được thực hiện trừ khi bạn có thể chứng minh rằng chúng là cần thiết.
Pete Becker

21
Nó chỉ là tối ưu hóa "quá sớm" nếu bạn cần phải làm việc với nó. Tôi thấy không có vấn đề gì khi áp dụng những thành ngữ hiệu quả thay vì những thành ngữ kém hiệu quả, cho dù nó có tạo ra sự khác biệt trong một ngữ cảnh cụ thể hay không.
Mark Ransom vào

11

Ngoài ra, hãy kiểm tra xem #includetệp tiêu đề chứa khai báo đầy đủ của lớp dẫn xuất có trong tệp nguồn của bạn hay không.

Tôi đã có vấn đề này. Các std::shared<derived>sẽ không cast vào std::shared<base>. Tôi đã khai báo trước cả hai lớp để tôi có thể giữ các con trỏ đến chúng, nhưng vì tôi không có #includetrình biên dịch nên không thể thấy rằng một lớp được dẫn xuất từ ​​lớp kia.


1
Chà, tôi không mong đợi điều đó nhưng điều này đã sửa nó cho tôi. Tôi đã rất cẩn thận và chỉ bao gồm các tệp tiêu đề khi tôi cần chúng để một số tệp chỉ có trong tệp nguồn và chuyển tiếp khai báo chúng trong tiêu đề như bạn đã nói.
jigglypuff

Trình biên dịch ngu ngốc là ngu ngốc. Đây là vấn đề của tôi. Cảm ơn bạn!
Tanveer Badar
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.