2009-08-11 70 views
27

This question đã được hỏi ở đây một vài giờ trước và làm cho tôi nhận ra rằng Tôi chưa bao giờ thực sự sử dụng các loại trả về biến đổi trong mã của riêng tôi. Đối với những người không chắc chắn hiệp phương sai là gì, nó cho phép loại trả về (thường) các hàm ảo khác nhau với điều kiện các loại này là một phần của cùng một hệ thống phân cấp thừa kế . Ví dụ:Khi nào C++ hiệp phương sai giải pháp tốt nhất?

struct A { 
    virtual ~A(); 
    virtual A * f(); 
    ... 
}; 

struct B : public A { 
    virtual B * f(); 
    ... 
}; 

Các kiểu trả về khác nhau của hai hàm f() được cho là có cùng biến đổi. phiên bản cũ của C++ yêu cầu các loại trở lại được như vậy, vì vậy B sẽ phải trông giống như:

struct B : public A { 
    virtual A * f(); 
    ... 
}; 

Vì vậy, câu hỏi của tôi: Có ai có một ví dụ thực tế nơi hiệp biến kiểu trả về của hàm ảo được yêu cầu , hoặc sản xuất một giải pháp vượt trội để chỉ đơn giản là trả về một con trỏ cơ bản hoặc tham chiếu?

+0

Khi tôi đọc câu hỏi, phản ứng của tôi giống hệt nhau. Vấn đề là gì - không sử dụng các loại trả về covariant! –

Trả lời

27

Ví dụ kinh điển là phương pháp .clone()/.copy(). Vì vậy, bạn luôn có thể làm

obj = obj->copy(); 

bất kể loại obj là gì.

Chỉnh sửa: Phương thức sao chép này sẽ được định nghĩa trong lớp cơ sở Đối tượng (vì nó thực sự là trong Java). Vì vậy, nếu bản sao không phải là biến thể, bạn sẽ phải đúc, hoặc sẽ bị hạn chế đối với các phương thức của lớp cơ sở gốc (mà sẽ chỉ có rất ít phương thức, so sánh lớp của đối tượng nguồn của bản sao).

+2

Nhưng bạn không nên (IMHO) quan tâm loại A * a = some_a_or_b-> clone(); cũng hoạt động tốt, và sau đó bạn sử dụng các phương thức ảo khác trên con trỏ nhân bản. –

+0

Tôi thấy hiệp phương sai hơn là đường cú pháp, sau đó là một tính năng cần thiết. Bạn luôn có thể làm A * a = b-> clone(); dynamic_cast (a) -> initB(); – Christopher

+5

Trừ trường hợp hiệp phương sai, loại được kiểm tra tĩnh. – AProgrammer

1

Ví dụ khác là nhà máy bê tông sẽ trả về con trỏ đến lớp bê tông thay vì lớp trừu tượng (tôi đã sử dụng nó để sử dụng nội bộ trong nhà máy khi nhà máy phải xây dựng đối tượng ghép).

+0

Có lẽ bạn dynamic_cast các con trỏ để làm việc ra những gì cụ thể dụ bạn đã thực sự có? Trong trường hợp đó bạn không cần hiệp phương sai. Hay tôi đang thiếu một cái gì đó? –

+0

'dynamic_cast' yêu cầu các loại có tính đa hình và có khả năng tốn kém. Tuy nhiên, kết quả tương tự có thể đạt được bằng cách sử dụng một mẫu chức năng trong lớp nhà máy. Điều này sẽ thực hiện hai nhiệm vụ: Kiểm tra xem các kiểu có liên quan (isbaseclass hay bất kỳ thứ gì) và sau đó đưa kết quả chung xuống loại có nguồn gốc. Không cần sử dụng năng động. –

+0

Bởi khả năng đắt tiền tôi có nghĩa là đắt hơn một static_cast và thậm chí sau đó nó chỉ là nơi bạn đang gọi nó hàng triệu lần mà nó làm cho một sự khác biệt. ;) –

4

Tôi nghĩ hiệp phương sai có thể hữu ích khi khai báo phương thức của nhà máy trả về một lớp cụ thể chứ không phải lớp cơ sở của nó. This article giải thích kịch bản này khá tốt, và bao gồm các ví dụ mã sau:

class product 
{ 
    ... 
}; 

class factory 
{ 
public: 
    virtual product *create() const = 0; 
    ... 
}; 

class concrete_product : public product 
{ 
    ... 
}; 

class concrete_factory : public factory 
{ 
public: 
    virtual concrete_product *create() const 
    { 
     return new concrete_product; 
    } 
    ... 
}; 
1

Nó trở nên hữu ích trong kịch bản mà bạn muốn sử dụng một nhà máy bê tông để tạo ra các sản phẩm bê tông. Bạn luôn muốn có giao diện chuyên biệt nhất đủ chung ...

sử dụng nhà máy bê tông có thể yên tâm giả sử rằng sản phẩm là sản phẩm cụ thể, vì vậy có thể sử dụng an toàn các tiện ích do lớp sản phẩm cụ thể cung cấp liên quan đến lớp trừu tượng. Điều này thực sự có thể được coi là đường cú pháp, nhưng nó ngọt ngào anyway.

13

Nói chung, hiệp phương sai cho phép bạn thể hiện nhiều thông tin hơn trong giao diện lớp dẫn xuất hơn là đúng trong giao diện lớp cơ sở. Hành vi của một lớp dẫn xuất là cụ thể hơn so với một lớp cơ sở, và hiệp phương sai biểu thị (một khía cạnh) sự khác biệt.

Tính năng này hữu ích khi bạn có phân cấp liên quan đến gubbins, trong trường hợp một số khách hàng muốn sử dụng giao diện lớp cơ sở, nhưng các ứng dụng khách khác sẽ sử dụng giao diện lớp dẫn xuất.Với const-đúng đắn bỏ qua:

class URI { /* stuff */ }; 

class HttpAddress : public URI { 
    bool hasQueryParam(string); 
    string &getQueryParam(string); 
}; 

class Resource { 
    virtual URI &getIdentifier(); 
}; 

class WebPage : public Resource { 
    virtual HttpAddress &getIdentifier(); 
}; 

Clients mà biết họ có một WebPage (trình duyệt, có lẽ) biết rằng nó có ý nghĩa để nhìn vào params truy vấn. Khách hàng đang sử dụng lớp cơ sở tài nguyên không biết điều đó. Họ sẽ luôn ràng buộc trả lại HttpAddress& thành số URI& biến hoặc tạm thời.

Nếu họ nghi ngờ, nhưng không biết, đối tượng tài nguyên của họ có HttpAddress, thì họ có thể dynamic_cast. Nhưng hiệp phương sai vượt trội hơn "chỉ cần biết" và làm diễn viên cho cùng một lý do mà gõ tĩnh là hữu ích cả.

Có các lựa chọn thay thế - gắn chức năng getQueryParam trên URI nhưng làm cho hasQueryParam trả về false cho mọi thứ (làm lộn xộn giao diện URI). Để lại WebPage::getIdentifier được xác định để trả lại URL&, thực sự quay lại HttpIdentifier& và yêu cầu người gọi thực hiện dynamic_cast vô nghĩa (cắt mã gọi và tài liệu về WebPage nơi bạn nói "URL được trả về được đảm bảo tự động gán cho HttpAddress"). Thêm một hàm getHttpIdentifier vào WebPage (cắt giao diện WebPage). Hoặc chỉ sử dụng hiệp phương sai cho những gì nó có nghĩa là để làm, đó là thể hiện một thực tế là một WebPage không có FtpAddress hoặc một MailtoAddress, nó có một HttpAddress.

Cuối cùng, tất nhiên, có một lý lẽ hợp lý rằng bạn không nên có thứ bậc của gubbins, hãy để một mình các phân cấp liên quan đến gubbins. Nhưng những lớp đó có thể dễ dàng là giao diện với các phương thức ảo thuần túy, vì vậy tôi không nghĩ nó ảnh hưởng đến tính hợp lệ của việc sử dụng hiệp phương sai.

+2

gubbins? Đó không phải là từ tôi quen thuộc. Googling nó tạo ra từ này UrbanDictionary.com: \t một gubbin là người nhân dịp hoạt động như một lang thang "bạn sẽ không tin cha mẹ sở hữu một căn biệt thự sẽ bạn Các gubbin?!" tôi đoán có một ý nghĩa khác nhau trong một. –

+0

'không sợ. Nó chỉ có nghĩa là "thứ", "mọi thứ". –

1

Tôi thường thấy mình sử dụng hiệp phương sai khi làm việc với mã hiện tại để loại bỏ static_casts. Thông thường tình trạng này là tương tự như sau:

class IPart {}; 

class IThing { 
public: 
    virtual IPart* part() = 0; 
}; 

class AFooPart : public IPart { 
public: 
    void doThis(); 
}; 

class AFooThing : public IThing { 
    virtual AFooPart* part() {...} 
}; 

class ABarPart : public IPart { 
public: 
    void doThat(); 
}; 

class ABarThing : public IThing { 
    virtual ABarPart* part() {...} 
};  

này cho phép tôi để

AFooThing* pFooThing = ...; 
pFooThing->Part()->doThis(); 

ABarThing pBarThing = ...; 
pBarThing->Part()->doThat(); 

thay vì

static_cast<AFooPart>(pFooThing->Part())->doThis(); 

static_cast<ABarPart>(pBarThing->Part())->doThat(); 

Bây giờ khi gặp mã như vậy, có thể tranh luận về thiết kế ban đầu và liệu có tốt hơn hay không - nhưng theo kinh nghiệm của tôi thường có những hạn chế như ưu tiên, chi phí/lợi ích vv. và chỉ cho phép các bước nhỏ như thế này.

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