2010-02-10 43 views
10

Tôi có một câu hỏi, mặc dù nó không giới hạn trong C++. Làm thế nào để trở về lớp hoàn toàn khác nhau từ một chức năng?Cách trả về các lớp khác nhau từ một hàm?

f() { 

in case one: return A; 
in case two: return B; 
in case three: return C; 


} 

Ví dụ, tôi có hai quả bóng trong không gian, theo vị trí và kích thước, có ba tình huống cho hai quả bóng để giao nhau với nhau, ví dụ, không ngã, vào thời điểm, một và khoanh tròn. Làm thế nào tôi có thể trả về các lớp khác nhau trong một hàm?

Cảm ơn.

+4

Ví dụ không giống như ba loại khác nhau là một giải pháp tốt. Âm thanh giống như ba trường hợp khác nhau của một khu vực. Bạn có thể đưa ra một ví dụ hay một mô tả thực sự về những gì bạn đang cố gắng làm. –

Trả lời

28

Nếu bạn có thể đủ khả năng Boost thì âm thanh này giống như một ứng dụng hoàn hảo cho Boost.Variant.

struct NoIntersection { 
    // empty 
}; 
struct Point { 
    // whatever 
}; 
struct Circle { 
    // whatever 
}; 

typedef boost::variant<NoIntersection, Point, Circle> IntersectionResult; 

IntersectionResult intersection_test() { 

    if(some_condition){ 
     return NoIntersection(); 
    } 
    if(other_condition){ 
     return Point(x, y); 
    } 
    if(another_condition){ 
     return Circle(c, r); 
    } 
    throw std::runtime_error("unexpected"); 
} 

Sau đó bạn xử lý kết quả của bạn với một người truy cập tĩnh:

struct process_result_visitor : public boost::static_visitor<> { 

    void operator()(NoIntersection) { 
     std::cout << "there was no intersection\n"; 
    } 
    void operator()(Point const &pnt) { 
     std::cout << "there was a point intersection\n"; 
    } 
    void operator()(Circle const &circle) { 
     std::cout << "there was a circle intersection\n"; 
    } 
}; 

IntersectionResult result = intersection_test(); 
boost::apply_visitor(process_result_visitor(), result); 

EDIT: Lớp khách phải xuất phát từ boost::static_visitor

UPDATE: Xuất phát từ một số ý kiến ​​chỉ trích tôi đã viết a little benchmark program.Bốn cách tiếp cận được so sánh:

  • boost::variant
  • đoàn
  • hệ thống phân cấp lớp
  • boost::any

Đây là những kết quả trong máy tính ở nhà của tôi, khi tôi biên dịch trong chế độ phát hành với tối ưu hóa mặc định (VC08):

thử nghiệm với boost :: biến mất 0,011 micro

thử nghiệm với công đoàn mất 0.012 micro giây

thử nghiệm với hệ thống phân cấp mất 0,227 micro

thử nghiệm với boost :: bất kỳ mất 0,188 micro

Sử dụng boost::variant nhanh hơn công đoàn và khách hàng tiềm năng (IMO) cho mã thanh lịch nhất. Tôi đoán rằng hiệu năng cực kỳ kém của phương pháp phân cấp lớp là do nhu cầu sử dụng phân bổ bộ nhớ động và công văn động. boost::any không phải là nhanh và cũng không đặc biệt thanh lịch vì vậy tôi sẽ không xem xét nó cho nhiệm vụ này (nó có các ứng dụng khác mặc dù)

+1

Cách tiếp cận thú vị và tốt hơn 'struct R {enum type {t1, t2, t3}; union {A a; B b; C c; } giá trị; } '... – xtofl

+2

+1. Cấu trúc khách truy cập có thêm lợi ích khi không biên dịch nếu bạn không xử lý một trong các trường hợp. –

+0

@xtofl, đã kiểm tra các nguồn, tôi sẽ không nói "tốt hơn". 'boost :: variant' theo cùng một ý tưởng, nhưng chỉ cần thêm đường cú pháp. –

13

Các lớp bạn muốn trả về phải được bắt nguồn từ một lớp cơ sở chung. Vì vậy, bạn có thể trả về kiểu cơ sở. Ví dụ (đây không phải là mã, chỉ cần đánh dấu mẫu, bạn có thể sử dụng giao diện nếu ngôn ngữ của bạn hỗ trợ lớp trừu tượng hoặc trừu tượng này. Ví dụ: Nếu bạn sử dụng C++, bạn sẽ phải trả lại con trỏ của lớp chung):

class A : public Common 
{ 
.. 
} 

class B : public Common 
{ 
.. 
} 

class C : public Common 
{ 
.. 
} 

Common f() { 

in case one: return A; 
in case two: return B; 
in case three: return C; 


} 
+1

làm thế nào về ba lớp học là hoàn toàn không liên quan? – skydoor

+9

Lưu ý rằng trong C++ bạn sẽ cần trả về một con trỏ * thành kiểu cơ sở. –

+13

@skydoor Đó sẽ là thiết kế tồi tệ, để nói rằng ít nhất. –

2

Để có thể làm bất cứ điều gì hữu ích với kết quả, bạn phải trả về một đối tượng có một baseclass chung. Trong trường hợp của bạn, bạn có thể muốn để A, B và C kế thừa từ một "lớp giao lộ" chung; một lớp phổ biến cho tất cả các đối tượng đại diện cho một số hình thức giao nhau. Hàm f của bạn sau đó sẽ trả về một đối tượng thuộc loại này.

2

Các lớp bạn muốn trả về phải có lớp hoặc giao diện cha mẹ chung.
Nếu những lớp đó không có điểm chung, điều đó, tôi cho là không đúng sự thật, bạn có thể trả lại object.
Tính năng này còn được gọi là đa hình.

1

Bạn không thể. Bạn chỉ có thể trả về một con trỏ cơ sở cho các lớp dẫn xuất khác nhau. Nếu điều này là hoàn toàn, 100% cần thiết, bạn có thể sử dụng ngoại lệ như một hack xấu xí, nhưng điều đó rõ ràng là không được đề nghị ở tất cả.

+0

Không bao giờ nói là không thể. Bạn có thể làm điều đó theo nhiều cách khác nhau, mặc dù tôi đã không xem xét ngoại lệ. –

+0

Các giải pháp tốt hơn đã được thể hiện bởi những người khác trong chủ đề này hơn là sử dụng ngoại lệ, một trong hai cách :) – gparent

2

Trong con trỏ lớp cơ sở c + + có thể trỏ đến đối tượng lớp dẫn xuất. Chúng tôi có thể sử dụng thực tế này để mã hóa một chức năng đáp ứng các yêu cầu của bạn:

class shape{}; 

class circle: public shape 
{}; 

class square: public shape 
{}; 

shape* function(int i){ // function returning a base class pointer. 

    switch(i) { 

     case 1: return new circle(); 

     case 2: return new square(); 

    } 
} 
0

Trong ngôn ngữ phản ánh, nó là dễ dàng hơn để đạt được. Trong cpp, nếu bạn có một tập các lớp tiêu chuẩn được trả về (con trỏ), hãy tạo một kiểu liệt kê và trả về giá trị enum. Sử dụng giá trị này, bạn có thể suy ra loại lớp. Đây là một cách chung trong trường hợp không có lớp cha mẹ chung

1

Thậm chí nếu bạn có thể trả lại ba loại đối tượng khác nhau từ hàm, bạn sẽ làm gì với kết quả? Bạn cần phải làm một cái gì đó như:

XXX ret_val = getIntersection(); 

Nếu getIntersection trở ba loại khác nhau của các đối tượng, XXX sẽ phải thay đổi dựa trên những gì getIntersectionđi trở lại. Rõ ràng điều này là khá không thể.

Để đối phó với điều này, bạn có thể xác định một loại xác định đủ để trang trải tất cả các khả năng:

class Intersection { 
    enum { empty, point, circle, sphere}; 
    point3D location; 
    size_t radius; 
}; 

Bây giờ getIntersection() có thể trở lại một Intersection xác định loại giao nhau bạn có (và BTW, bạn cần để xem xét khả năng thứ tư: với hai hình cầu cùng bán kính và cùng một điểm trung tâm, giao lộ sẽ là một hình cầu) và kích thước và vị trí của giao lộ đó.

1

Có một tùy chọn khác khả dụng. Bạn có thể trả lại một số union của con trỏ đến các đối tượng cùng với thẻ cho người gọi biết thành viên nào của công đoàn hợp lệ. Một cái gì đó như:

struct result { 
    enum discriminant { A_member, B_member, C_member, Undefined } tag; 
    union result_data { 
     A *a_object; 
     B *b_object; 
     C *c_object; 
    } data; 
    result(): tag(Undefined) {} 
    explicit result(A *obj): tag(A_member) { data.a_object = obj; } 
    explicit result(B *obj): tag(B_member) { data.b_object = obj; } 
    explicit result(C *obj): tag(C_member) { data.c_object = obj; } 
}; 

Tôi có thể sử dụng Boost.variant as suggested by Manuel nếu bạn có tùy chọn.

6

Ngoài đề xuất Boost.Variant của @ Manuel, hãy xem Boost.Any: có mục đích tương tự như Boost.Variant nhưng các sự cân bằng và chức năng khác nhau.

boost :: bất kỳ không bị chặn (có thể giữ bất kỳ loại nào) trong khi tăng :: biến thể bị ràng buộc (loại được hỗ trợ được mã hóa theo loại biến thể, vì vậy nó chỉ có thể chứa giá trị của các loại này).

// from Beyond the C++ Standard Library: An Introduction to Boost 
// By Björn Karlsson 

#include <iostream> 
#include <string> 
#include <utility> 
#include <vector> 
#include "boost/any.hpp" 

class A { 
public: 
    void some_function() { std::cout << "A::some_function()\n"; } 
}; 

class B { 
public: 
    void some_function() { std::cout << "B::some_function()\n"; } 
}; 

class C { 
public: 
    void some_function() { std::cout << "C::some_function()\n"; } 
}; 

int main() { 
    std::cout << "Example of using any.\n\n"; 

    std::vector<boost::any> store_anything; 

    store_anything.push_back(A()); 
    store_anything.push_back(B()); 
    store_anything.push_back(C()); 

    // While we're at it, let's add a few other things as well 
    store_anything.push_back(std::string("This is fantastic! ")); 
    store_anything.push_back(3); 
    store_anything.push_back(std::make_pair(true, 7.92)); 

    void print_any(boost::any& a); 
    // Defined later; reports on the value in a 

    std::for_each(
    store_anything.begin(), 
    store_anything.end(), 
    print_any); 
} 

void print_any(boost::any& a) { 
    if (A* pA=boost::any_cast<A>(&a)) { 
    pA->some_function(); 
    } 
    else if (B* pB=boost::any_cast<B>(&a)) { 
    pB->some_function(); 
    } 
    else if (C* pC=boost::any_cast<C>(&a)) { 
    pC->some_function(); 
    } 
} 
+1

Tôi muốn thấy rằng chức năng 'print_any' hơn. – Novelocrat

+0

Mã được cập nhật. –

+1

Thay vì lenghty, và chắc chắn ít tự tài liệu rằng một 'biến thể' vì bất cứ điều gì có thể được trả về ... ngoài ra, có hiệu suất trên không để xem xét,' any' yêu cầu kiểm tra thời gian chạy kiểu bằng RTTI trong khi 'biến thể 'lưu trữ kiểu trong một thành viên dữ liệu. –

0

Giới hạn dựa trên loại trả về được khai báo của phương pháp của bạn. Mã của bạn nói rằng:

f() { 
    in case one: return A; 
    in case two: return B; 
    in case three: return C; 
} 

Khi trong thực tế trình biên dịch đòi hỏi một cái gì đó như thế này:

FooType f() { 
    in case one: return A; 
    in case two: return B; 
    in case three: return C; 
} 

Nó phải có khả năng chuyển đổi A, B, và C đến một FooType, thường thông qua thừa kế đơn giản, mặc dù tôi sẽ không nhận được sự khác biệt giữa các lớp con so với phân loại phụ.

Có các phương pháp có thể giải quyết vấn đề này. Bạn có thể tạo một lớp hoặc cấu trúc (C++) có các trường cho mỗi kiểu trả về có thể khác nhau và sử dụng một số trường cờ để chỉ ra trường nào là giá trị trả về thực tế.

class ReturnHolder { 
    public int fieldFlag; 
    public TypeA A; 
    public TypeB B; 
    public TypeC C; 
} 

Ví dụ về enum trong câu trả lời khác giống nhau. Lý do tại sao đó là một hack là đoạn code để xử lý sự trở lại từ phương pháp này sẽ phải có rất nhiều mã để xử lý mỗi possibilites khác nhau, như vậy

main(){ 

FooType *x = new FooType(); 
ReturnHolder ret = x.f(); 

switch (ret.fieldFlag) 
    case: 1 
     //read ret.A 

    case: 2 
     //read ret.B 

    case: 3 
     //read ret.C 

} 

Và đó là thậm chí không cần đi sâu vào cố gắng làm điều đó với các ngoại lệ giới thiệu những vấn đề lớn hơn. Có lẽ tôi sẽ thêm điều đó vào sau này dưới dạng bản chỉnh sửa.

1

Và bằng cách này, như bạn nói câu hỏi rằng "không giới hạn C++":

1) ngôn ngữ động, tất nhiên, làm cho nó miếng bánh:

# python 
def func(i): 
    if i == 0: 
    return 0 
    elif i == 1: 
    return "zero" 
    else 
    return() 

2) một số ngôn ngữ chức năng (Haskell, OCaml, Scala, F #) cung cấp các biến thể tích hợp sẵn được gọi là Algebraic Data Types (bài viết có mẫu tốt).

0

Bạn thực sự không muốn làm điều đó, và thực sự nên đưa ra một thiết kế tốt hơn thay vì buộc một cái chốt vuông trong một lỗ tròn. Và với hầu hết các ngôn ngữ, bạn không thể làm gì cả, theo thiết kế. Bạn sẽ không bao giờ thực sự biết những gì bạn đang làm việc với, và sẽ không trình biên dịch trước thời hạn, đảm bảo thêm lỗi và hành vi kỳ lạ và không thể hiểu được.

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