2010-09-07 32 views
12

Tôi có một hệ thống phân cấp lớp, và mỗi lớp trong đó có một lớp ngoại lệ, có nguồn gốc trong một hệ thống phân cấp song song, do đó ...Mở rộng enum trong các lớp thừa

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class BaseException : public std::exception 
{ 
    enum {THIS_REASON, THAT_REASON}; 
}; 

class DerivedException : public BaseException 
{ 
    // er...what? 
}; 

Tôi muốn, trong lớp DerivedException, để mở rộng kiểu liệt kê để bao gồm một giá trị mới THE_OTHER_REASON, sao cho lớp DerivedException có thể chứa bất kỳ giá trị nào trong ba giá trị này.

Trước hết, tôi có muốn làm điều này không? Liệu nó có vẻ là một thực hành hợp lý? Nếu vậy, làm thế nào để tôi đi về nó? Nếu không, bạn sẽ giới thiệu những lựa chọn thay thế nào?

EDIT: Một bản sao có thể đã được đề xuất here, nhưng các giải pháp được đề xuất khác nhau vì câu hỏi đó là dành cho C# và điều này cho C++.

+0

thể trùng lặp của [Enum "thừa kế"] (http: // stackoverflow.com/questions/757684/enum-inheritance) –

Trả lời

10

Từ quan điểm của OO, điều đó là không hợp lý. Vì bạn nói rằng DerivedExceptionlà-aBaseException, các lý do có thể của nó phải là tập hợp con trong số đó là BaseException, không phải là siêu âm. Nếu không, bạn cuối cùng sẽ phá vỡ Liskov Substitution Principle.

Hơn nữa, vì C++ enums không phải là lớp, bạn không thể mở rộng hoặc kế thừa chúng. Bạn có thể xác định lý do bổ sung trong một enum riêng biệt trong DerivedException, nhưng sau đó cuối cùng bạn đụng vào cùng một vấn đề đã nêu ở trên:

class DerivedException : public BaseException 
{ 
    enum { 
    SOME_OTHER_REASON = THAT_REASON + 256, // allow extensions in the base enum 
    AND_ANOTHER_REASON 
    }; 
    ... 
}; 

... 
try { 
    ... 
} catch (BaseException& ex) { 
    if (ex.getReason() == BaseException::THIS_REASON) 
    ... 
    else if (ex.getReason() == BaseException::THAT_REASON) 
    ... 
    else if (ex.getReason() == ??? what to test for here ???) 
    ... 
} 

gì bạn có thể làm thay vào đó là định nghĩa một lớp ngoại lệ riêng biệt cho mỗi lý do khác nhau. Sau đó, bạn có thể xử lý chúng đa hình (nếu cần). Đây là cách tiếp cận của thư viện C++ chuẩn cũng như các thư viện lớp khác. Vì vậy, bạn tuân thủ các quy ước, điều này làm cho mã của bạn dễ hiểu hơn.

+0

+1 cho Nguyên tắc thay thế Liskov. Nếu mở rộng thì bạn cần một số loại ánh xạ trở lại bộ mã cơ sở. –

+0

nhưng điều này có cùng hạn chế hơn tôi. Bạn không thể chắc chắn nếu giá trị không phải là từ enum khác khi bạn sử dụng nhiều hơn 2 enums bắt nguồn – kravemir

+0

@Miro, dường như bạn không nhận thấy rằng tôi hoàn toàn không khuyên bạn mở rộng enums theo bất kỳ cách nào :-) –

2

Bạn có thể làm điều này, vì bạn sử dụng các enums chưa được đặt tên. Tôi cho rằng bạn sử dụng kiểu số nguyên để lưu trữ lý do ngoại lệ.

class DerivedException : public BaseException 
{ 
    enum { YET_ANOTHER_REASON = THAT_REASON + 1, EVEN_MORE_REASON}; 
}; 

Tôi sẽ không mã hóa lý do làm mã. Tôi thích các lớp ngoại lệ chuyên biệt hơn, vì vậy tôi chỉ có thể bắt các loại ngoại lệ mà tôi có thể xử lý tại một thời điểm.

1

Đầu tiên: không thể bắt nguồn từ enums; bạn không may mắn ở đó.

Thứ hai, có vẻ như bạn đang suy nghĩ quá mức về mô hình ngoại lệ của mình. Thay vì có mọi lớp sử dụng ngoại lệ có nguồn gốc tùy chỉnh cụ thể cho lớp đó, hãy tạo một loạt các ngoại lệ có giá trị ngữ nghĩa mà lớp học của bạn có thể ném. Ví dụ: nếu bạn đang ném bởi vì đối số bắt buộc là null, bạn có thể ném một số NullArgumentException. Nếu bạn đang ném vì tràn bộ nhớ, bạn có thể ném một số ArithmeticOverflowException.

Trong mô hình enum của bạn, bạn đang đặt giá trị ngữ nghĩa trên điều tra; Tôi đề nghị bạn đặt giá trị ngữ nghĩa vào loại ngoại lệ. Bạn có thể bắt được nhiều loại ngoại lệ, hãy nhớ.

Ví dụ về ngoại lệ có giá trị ngữ nghĩa, hãy xem thư viện C++ chuẩn hoặc để có danh sách mở rộng hơn, thư viện Java hoặc C#.

1

Không, nó không hợp lý vì nó đứng ngay bây giờ. Đối với kiểu dẫn xuất có bất kỳ ý nghĩa nào (từ quan điểm của nguyên tắc thay thế của Liskov), cần phải có hành vi đa hình trong lớp cơ sở.Bạn có thể thêm virtual int GetError() const vào lớp cơ sở và để cho các lớp dẫn xuất ghi đè lên nó, nhưng sau đó người dùng của BaseException* hoặc BaseException& sẽ không có bất kỳ đầu mối nào về mã lỗi được trả về bởi các lớp dẫn xuất có nghĩa là gì.

Tôi muốn tách các giá trị mã lỗi khỏi các lớp.

9

Với tôi, có vẻ hợp lý hơn khi có một lớp ngoại lệ cho mỗi lý do ngoại lệ. Khi xử lý một ngoại lệ, thường không thú vị khi biết lớp nào đã ném ngoại lệ, nhưng vì lý do ngoại lệ nào được ném ra.

Nếu bạn muốn giữ lại thiết kế của bạn: C++ không cho phép bạn mở rộng một enum hiện có, nhưng bạn có thể tạo một enum mới bắt đầu nơi một trước rời đi:

class BaseException : public std::exception 
{ 
    enum {THIS_REASON, THAT_REASON, END_OF_BASE_REASONS }; 
}; 

class DerivedException : public BaseException 
{ 
    enum {OTHER_REASON = BaseException::END_OF_BASE_REASONS }; 
}; 
1

tôi sẽ giống như, trong lớp DerivedException, để mở rộng kiểu liệt kê để bao gồm một giá trị mới THE_OTHER_REASON, sao cho lớp DerivedException có thể chứa bất kỳ giá trị nào trong ba giá trị này.

Chỉ cần gán giá trị đầu tiên của một enum mới. Điều này làm việc kể từ khi bạn chỉ sử dụng enum như một cách để khai báo các hằng số.

class DerivedException : public BaseException 
{ 
    enum {THE_OTHER_REASON = THAT_REASON + 1, THE_REALLY_OTHER_REASON, ETC}; 
}; 

Vấn đề là bạn có thể không thực sự mong đợi độc đáo của sự đếm giữa các lớp thừa kế, mà không xác định một "chuỗi" của các lớp thừa kế (tức là bắt đầu đếm của nó sau của người khác). Tuy nhiên, nếu bạn chỉ cần duy nhất trong một nhánh cây ngoại lệ, nó có thể hoạt động tốt.

0

"Lý do" của ngoại lệ rất có thể là thứ cần được hiển thị cho người dùng hoặc đăng nhập, do đó, chuỗi có vẻ phù hợp hơn, vì vậy nó có thể được sử dụng trong hàm().
Nếu có nhiều hơn thế, cần có nhiều lớp con hơn.

1

Không có bất cứ cách nào có nguồn gốc để làm điều đó, nhưng nó có thể dễ dàng thực hiện với định nghĩa: Dưới đây là ví dụ nhỏ:

#define _Enum1_values a, b, c, d 

enum Enum1 { 
    _Enum1_values 
}; 

// there aren't strongly typed enums 
class A { 
public: 
    enum Enum2 { 
     _Enum1_values, e, f 
    }; 
}; 

// there aren't strongly typed enums 
class B { 
public: 
    enum Enum3 { 
     _Enum1_values, g, h 
    }; 
}; 


#include <iostream> 

int main() { 
    std::cout << "Enum1::d: " << d << '\n'; 
    std::cout << "Enum2::d: " << A::d << '\n'; 
    std::cout << "Enum2::e: " << A::e << '\n'; 
    std::cout << "WARNING!!!: Enum2::e == Enum3::g: " << (A::e == B::g) << '\n'; 
} 
+1

Ngoài giá trị giống hệt nhau giữa các enums "có nguồn gốc", vấn đề nghiêm trọng hơn là bất kỳ phần mở rộng của 'Enum1' phá vỡ enums" có nguồn gốc ". Việc cập nhật chúng không thể được thực hiện tự động, và chỉ dựa vào kỷ luật và bộ nhớ của các nhà phát triển là dễ bị thất bại trong thời gian dài. –