2013-07-13 44 views
20

Có cách nào để chỉ định hàm tạo mặc định của enum class không?Người dùng xác định C++ 11 enum class Mặc định Constructor

Tôi đang sử dụng enum class để chỉ định một tập hợp các giá trị được phép cho một kiểu dữ liệu cụ thể trong thư viện: trong trường hợp này, đó là số id pin GPIO của Raspberry Pi. Nó trông giống như sau:

enum class PinID : int {N4 = 4, N17 = 17, /* ...etc... */ }

Điểm của tôi làm điều này thay vì chỉ sử dụng, chẳng hạn, một int là để đảm bảo mã đó là an toàn: Tôi static_assert (hoặc nếu không có thể biên dịch thời gian đảm bảo - phương pháp thực tế được sử dụng không quan trọng đối với tôi) những thứ như ai đó đã không mắc lỗi chính tả (chuyển số 5 thay vì số 4, v.v.) và tôi nhận được thông báo lỗi tự động cho các loại không khớp, v.v.

vấn đề sau đó là enum class có một hàm tạo mặc định - vì lợi ích của khả năng tương thích với C của enum tôi giả định (vì chúng có thứ hành vi tương tự) - khởi tạo số enum class tương đương 0. Trong trường hợp này, không có giá trị 0. Điều này có nghĩa rằng người dùng thực hiện một tuyên bố/định nghĩa như:

PinID pid = PinID();

là nhận được một Enumerator mà không được định nghĩa một cách rõ ràng (và thậm chí không có vẻ "tồn tại" khi người ta nhìn vào mã), và có thể dẫn đến lỗi thời gian chạy. Điều này cũng có nghĩa là các kỹ thuật như số switch ing trên các giá trị của các điều tra được xác định rõ ràng là không thể mà không có lỗi/trường hợp mặc định - cái gì tôi muốn tránh, vì nó buộc tôi hoặc throw hoặc thực hiện một cái gì đó như trả lại boost::optional. amenable để phân tích tĩnh.

Tôi đã cố gắng xác định hàm tạo mặc định không có kết quả. Tôi (tuyệt vọng) đã cố gắng để xác định một chức năng mà chia sẻ tên của enum class, nhưng điều này (khá đáng ngạc nhiên) dẫn đến lỗi trình biên dịch lạ. Tôi muốn giữ lại khả năng đúc enum class thành int, với tất cả các bản đồ liệt kê N# liệt kê thành # tương ứng của chúng, do đó, chỉ đơn thuần là "xác định", giả sử, N4 = 0 là không thể chấp nhận; điều này là vì sự đơn giản và lành mạnh.

Tôi đoán câu hỏi của mình là hai lần: có cách nào để có được loại an toàn tĩnh sau khi sử dụng enum class không? Nếu không, những khả năng nào khác sẽ thích? Những gì tôi muốn là cái gì đó:

  1. là mặc định constructable
  2. có thể được thực hiện để mặc định xây dựng một giá trị hợp lệ tùy ý
  3. cung cấp "tập hữu hạn các quy định" giá trị tạo nên bởi enum class es
  4. là ít nhất là an toàn như kiểu một enum class
  5. (tốt nhất) không liên quan đến đa hình runtime

Lý do tôi muốn xây dựng mặc định là vì tôi dự định sử dụng boost::lexical_cast để giảm chi phí liên quan đến chuyển đổi giữa các giá trị enum class và thực tế liên quan đến string s mà tôi đã xuất sang hệ điều hành (sysfs trong trường hợp này); boost::lexical_cast yêu cầu khả năng xây dựng mặc định.

Lỗi trong lý do của tôi được hoan nghênh - tôi bắt đầu nghi ngờ rằng enum class es là đối tượng phù hợp cho công việc sai, trong trường hợp này; làm rõ sẽ được cung cấp nếu được hỏi. Cảm ơn bạn đã dành thời gian.

Trả lời

12

Loại được xác định với enum class hoặc enum struct không phải là một lớp nhưng có liệt kê phạm vi và không thể có hàm tạo mặc định được xác định. Tiêu chuẩn C++ 11 xác định rằng câu lệnh PinID pid = PinID(); của bạn sẽ cho phép khởi tạo không. Trường hợp PinID được định nghĩa là enum class. Nó cũng cho phép các loại enum nói chung để giữ các giá trị khác với hằng số đếm.

Để hiểu rằng PinID() cho zero khởi đòi hỏi đọc phần chuẩn 3.9.9, 8.5.5,; 8.5.78.5.10 với nhau:

8.5.10 - An object whose initializer is an empty set of parentheses, i.e.,(), shall be value-initialized

8.5.7 - To value-initialize an object of type T means: ... otherwise, the object is zero-initialized.

8.5.5 - To zero-initialize an object or reference of type T means: — if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;

3.9.9 - Các loại liệt kê là một phần của tập hợp các loại được gọi là loại vô hướng.

Một giải pháp khả thi:

Để đáp ứng điểm của bạn 1-5 bạn có thể viết một lớp dọc theo dòng:

class PinID 
{ 
private: 
    PinID(int val) 
    : m_value(val) 
    {} 

    int m_value; 

public: 
    static const PinID N4; 
    static const PinID N17; 
    /* ...etc... */ 

    PinID() 
    : m_value(N4.getValue()) 
    {} 

    PinID(const PinID &id) 
    : m_value(id.getValue()) 
    {} 

    PinID &operator = (const PinID &rhs) 
    { 
     m_value = rhs.getValue(); 
     return *this; 
    } 

    int getValue() const 
    { 
     return m_value; 
    } 

    // Attempts to create from int and throw on failure. 
    static PinID createFromInt(int i); 

    friend std::istream& operator>>(std::istream &is, PinID &v) 
    { 
     int candidateVal(0); 
     is >> candidateVal; 
     v = PinID::createFromInt(candidateVal); 
     return is; 
    } 
}; 

const PinID PinID::N4 = PinID(4); 
/* ...etc... */ 

Điều đó có thể cung cấp cho bạn một cái gì đó mà bạn sẽ cần phải thực hiện cụ thể nỗ lực để có được một giá trị không hợp lệ vào. Nhà xây dựng và nhà điều hành luồng mặc định sẽ cho phép nó hoạt động với lexical_cast.

Dường như nó phụ thuộc vào mức độ quan trọng của các hoạt động trên PinID sau khi tạo nó cho dù nó đáng để viết một lớp hay chỉ xử lý các giá trị không hợp lệ ở khắp mọi nơi.

+1

Chỉ cần nhìn vào đó, có vẻ tốt, nhưng nó có hoạt động không? Bạn có một định nghĩa lớp bao gồm một thể hiện của chính nó như là một thành viên ... oh nhưng chúng là tĩnh. Trình biên dịch không phát điên sau đó. Gấu thông minh. – FizzixNerd

+0

@ FizzixNerd Tôi đã không chạy nó mặc dù một trình biên dịch nhưng nó sẽ làm việc ... Tôi nghĩ rằng nếu bạn thêm các nhà khai thác dòng bạn sẽ có thể sử dụng nó trực tiếp với lexical_cast, tôi giả sử ném một ngoại lệ nếu một giá trị không hợp lệ đến. – PeterSW

+0

@PeterSW, bạn có thể chỉ ra, mục nào trong C++ 11 standart để bạn tham khảo, nói về zero-initialization? –

4

An enum class chỉ được đánh số mạnh enum; nó không phải là class. C++ 11 vừa sử dụng lại từ khóa class hiện tại để tránh giới thiệu từ khóa mới có thể phá vỡ tính tương thích với mã C++ cũ.

Đối với câu hỏi của bạn, không có cách nào để đảm bảo tại thời điểm biên dịch rằng một diễn viên liên quan đến một ứng viên phù hợp. Hãy xem xét:

int x; 
std::cin >> x; 
auto p = static_cast<PinID>(x); 

Điều này là hoàn toàn hợp pháp và không có cách nào để đảm bảo tĩnh người dùng bảng điều khiển đã làm điều đúng.

Thay vào đó, bạn sẽ cần phải kiểm tra khi chạy rằng giá trị hợp lệ. Để giải quyết vấn đề này theo cách tự động, một trong những đồng nghiệp của tôi đã tạo một máy phát điện enum để tạo các kiểm tra này cùng với các thủ tục hữu ích khác được cung cấp cho một tệp có giá trị điều tra. Bạn sẽ cần phải tìm một giải pháp phù hợp với mình.

+0

Cảm ơn câu trả lời! Tôi biết một lớp enum không phải là một lớp, nhưng nó vẫn có một hàm tạo mặc định. Đối với kiểm tra tĩnh, tôi tất nhiên biết rằng tôi không thể kiểm tra tĩnh các chuyển đổi từ string-> enum - vì tôi sử dụng boost :: optioanls, nhưng tôi _can_ kiểm tra tĩnh ít nhất _some_ của enum- > chuỗi liên quan đến constexpr, phải không? – FizzixNerd

+3

@FizzixNerd "có hàm tạo mặc định" - Về mặt kỹ thuật, không có. Nó có một hành vi được định nghĩa để khởi tạo giá trị.Giá trị khởi tạo của một đối tượng kiểu lớp gọi một hàm tạo mặc định; khởi tạo giá trị của một đối tượng kiểu số hoặc enum đặt nó thành 0; khởi tạo giá trị của một đối tượng kiểu con trỏ đặt nó thành một giá trị con trỏ null. – aschepler

1

Tôi biết rằng câu hỏi này là ngày và rằng nó đã có một câu trả lời chấp nhận nhưng đây là một kỹ thuật có thể giúp trong một tình huống như thế này với một số tính năng mới của C++

Bạn có thể khai báo biến của lớp này hoặc là non static hoặc static, nó có thể được thực hiện theo nhiều cách được phép hỗ trợ trình biên dịch hiện tại của bạn.


Non tĩnh:

#include <iostream> 
#include <array> 

template<unsigned... IDs> 
class PinIDs { 
private: 
    const std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public: 
    PinIDs() = default; 
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

tĩnh:- Có 3 cách để viết những dòng này: (First Một - C++ 11 hoặc 14 hoặc cao hơn) cuối cùng 2 (C++ 17).

Đừng báo cho tôi phần C++ 11; Tôi không hoàn toàn chắc chắn khi các mẫu variadic hoặc các gói tham số được giới thiệu lần đầu tiên.

template<unsigned... IDs> 
class PinIDs{ 
private:   
    static const std::array<unsigned, sizeof...(IDs)> ids; 
public:  
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

template<unsigned... IDs> 
const std::array<unsigned, sizeof...(IDs)> PinIDs<IDs...>::ids { IDs... }; 

template<unsigned... IDs> 
class PinIDs{ 
private: 
    static constexpr std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public: 
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

template<unsigned... IDs> 
class PinIDs{ 
private: 
    static inline const std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public:  
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

Tất cả các ví dụ trên hoặc không tĩnh hay tĩnh làm việc với các trường hợp sử dụng tương tự dưới đây và cung cấp kết quả chính xác:

int main() { 
    PinIDs<4, 17, 19> myId; 

    std::cout << myId[0] << " "; 
    std::cout << myId[1] << " "; 
    std::cout << myId[2] << " "; 

    std::cout << "\nPress any key and enter to quit." << std::endl; 
    char c; 
    std::cin >> c; 

    return 0; 
} 

Output

4 17 19 
Press any key and enter to quit. 

Với kiểu này lớp mẫu sử dụng một danh sách tham số variadic, bạn không cần phải sử dụng bất kỳ constructor nhưng mặc định. Tôi đã thêm giới hạn kiểm tra vào mảng để các operator[] không vượt quá giới hạn của kích thước của nó; Tôi có thể ném một lỗi nhưng với unsigned loại tôi chỉ đơn giản là trả về -1 như là một giá trị không hợp lệ.

Với loại này, không có mặc định khi bạn phải khởi tạo loại đối tượng này thông qua danh sách tham số mẫu với một hoặc một bộ giá trị. Nếu một người muốn họ có thể specialize this class với một tham số duy nhất là 0 cho loại mặc định. Khi bạn khởi tạo loại đối tượng này; nó là cuối cùng như trong nó không thể được thay đổi từ tuyên bố của nó. Đây là đối tượng const và vẫn giữ nguyên cấu hình mặc định.

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