2011-11-06 35 views
9

Có lẽ một cái gì đó tương tự đã được hỏi, và chắc chắn, đó là một nitpick ...constexpr và khởi

tôi có một loạt các hằng số std::map s để chuyển đổi giữa enum (class) giá trị và std::string cơ quan đại diện của họ (cả hai cách). Một người nào đó ở đây đã chỉ ra với tôi rằng những bản đồ này sẽ được khởi tạo trong thời gian chạy, khi mã khởi tạo khác được chạy, trước khi chương trình của tôi thực thi tất cả những thứ tốt. Điều này có nghĩa là các biểu thức liên tục sẽ tác động đến hiệu suất thời gian chạy, vì các bản đồ được xây dựng từ các cặp enum-string của chúng.

Như một ví dụ minh họa, đây là một ví dụ về một trong những tấm bản đồ:

enum class os 
{ 
    Windows, 
    Linux, 
    MacOSX 
}; 
const map<string, os> os_map = 
    { {"windows", os::Windows}, 
     {"linux", os::Linux}, 
     {"mac",  os::MacOSX} }; 
const map<os, string> os_map_inverse = 
    { {os::Windows, "windows"}, 
     {os::Linux, "linux"}, 
     {os::MacOSX, "mac"} }; 

C++ 11 constexpr Sẽ có bất kỳ ảnh hưởng đến hiệu suất, hoặc là giả định của tôi về một thời gian chạy phạt khởi sai? Tôi nghĩ rằng một trình biên dịch có thể nhúng một container STL liên tục như dữ liệu thuần túy trong thực thi, nhưng dường như điều đó có thể không dễ dàng như tôi làm cho nó âm thanh?

+1

Tại sao không bạn thử 'boost :: bimap' cho ánh xạ hai mặt giữa enum và biểu diễn chuỗi của nó? Ít có khả năng gây ra lỗi khi thêm các giá trị mới. – Xeo

+0

Xeo: kéo Boost cho một thứ đơn giản như thế này? Không, cảm ơn, tôi phụ thuộc miễn phí, và thực sự muốn giữ nó theo cách đó;) ... Tôi thậm chí có thể thay thế bản đồ string-> enum bằng một 'unordered_map' và bản đồ chuỗi enum-> bằng một' vector '(giá trị enum không quan trọng, chúng chỉ đếm từng cái một) để thực hiện nếu điều đó có thể cải thiện được. 'boost :: bimap' sẽ hút so sánh :) – rubenvb

+2

@rubenvb: Và [Boost.MultiIndex] (http://www.boost.org/libs/multi_index/) có thể làm chính xác điều đó, ngắn gọn hơn nhiều, với 0 trên không. Vui lòng không xem Tăng cường dưới dạng 'phụ thuộc'. – ildjarn

Trả lời

17

Nó không phải là quá nhiều hiệu suất khởi tạo đó là một vấn đề, nhưng thứ tự khởi tạo. Nếu ai đó sử dụng bản đồ của bạn trước khi main đã bắt đầu (ví dụ như lúc khởi tạo biến phạm vi không gian tên), thì bạn là SOL, vì bạn không được đảm bảo rằng bản đồ của bạn đã được khởi tạo trước khi khởi tạo của người dùng sử dụng nó.

Tuy nhiên, bạn có thể thực hiện việc này lúc biên dịch. Chuỗi ký tự là các biểu thức không đổi, cũng như các điều tra viên.Một cấu trúc phức tạp đơn giản tuyến tính thời gian

struct entry { 
    char const *name; 
    os value; 
}; 

constexpr entry map[] = { 
    { "windows", os::Windows }, 
    { "linux", os::Linux }, 
    { "mac", os::Mac } 
}; 

constexpr bool same(char const *x, char const *y) { 
    return !*x && !*y ? true : (*x == *y && same(x+1, y+1)); 
} 

constexpr os value(char const *name, entry const *entries) { 
    return same(entries->name, name) ? entries->value : value(name, entries+1); 
} 

Nếu bạn sử dụng value(a, b) trong một bối cảnh biểu hiện liên tục, và tên bạn chỉ định không tồn tại, bạn sẽ nhận được một lỗi thời gian biên dịch vì cuộc gọi chức năng sẽ trở thành không đổi .

Để sử dụng value(a, b) trong ngữ cảnh biểu thức không liên tục, bạn nên thêm các tính năng an toàn, như thêm điểm đánh dấu cuối vào mảng của mình và ném một ngoại lệ trong value nếu bạn nhấn điểm đánh dấu kết thúc (cuộc gọi hàm sẽ vẫn là một hằng số) biểu hiện miễn là bạn không bao giờ nhấn điểm đánh dấu kết thúc).

+0

Dường như nó không hoạt động (GCC 4.5.1): http://ideone.com/w8QFN. Bạn có nghĩ rằng đó là một vấn đề trình biên dịch? – atzz

+0

@ yeszz có vấn đề về trình biên dịch. Hãy thử GCC4.6. –

+0

Johannes, cảm ơn bạn đã trả lời; Tôi sẽ đến vào ngày mai. Không có trình biên dịch có sẵn tại thời điểm này. – atzz

4

constexpr không hoạt động trên các biểu thức tùy ý và đặc biệt không phải trên những thứ sẽ sử dụng freestore. map/string sẽ sử dụng freestore, và do đó constexpr sẽ không hoạt động để khởi tạo chúng tại thời gian biên dịch, và không có mã chạy khi chạy. Đối với hình phạt thời gian chạy: Tùy thuộc vào thời gian lưu trữ của các biến đó (và tôi giả định tĩnh ở đây, có nghĩa là khởi tạo trước chính), bạn thậm chí sẽ không thể đo hình phạt, đặc biệt là không có trong mã sử dụng chúng, giả sử bạn sẽ sử dụng chúng nhiều lần, nơi tra cứu có nhiều "phí" hơn so với khởi tạo.

Nhưng đối với mọi thứ, hãy nhớ quy tắc một: Làm mọi thứ hoạt động. Hồ sơ. Làm mọi thứ nhanh. Theo thứ tự này.

3

Có, đó là một vấn đề điển hình.

Cách thay thế duy nhất tôi đã tìm thấy để tránh khởi chạy thời gian chạy này là sử dụng cấu trúc C đơn giản. Nếu bạn sẵn sàng để đi thêm dặm và lưu trữ các giá trị trong một mảng C đơn giản, bạn có thể nhận được khởi tạo tĩnh (và giảm bộ nhớ bộ nhớ).

Đó là một trong những lý do LLVM/Clang thực sự sử dụng tiện ích tblgen: các bảng đơn giản được khởi tạo tĩnh và bạn có thể tạo chúng được sắp xếp.

Một giải pháp khác là tạo một hàm chuyên dụng thay vì: để chuyển đổi chuỗi thành dễ dàng sử dụng chuyển đổi và để trình biên dịch tối ưu hóa nó thành một bảng đơn giản. nếu các nhánh khác được tổ chức đúng để có được hành vi O (log N) nhưng đối với các enums nhỏ thì tìm kiếm tuyến tính vẫn tốt, trong trường hợp một macro hack (dựa trên Boost Preprocessor goodness) có thể giúp bạn có mọi thứ bạn cần.

0

Gần đây tôi đã tìm thấy cách tốt nhất của riêng mình để cung cấp enum. Xem mã:

enum State { 
    INIT, OK, ENoFou, EWroTy, EWroVa 
}; 

struct StateToString { 
    State state; 
    const char *name; 
    const char *description; 
public: 
    constexpr StateToString(State const& st) 
      : state(st) 
      , name("Unknown") 
      , description("Unknown") 
    { 
     switch(st){ 
      case INIT : name = "INIT" , description="INIT";    break; 
      case OK : name = "OK" , description="OK";    break; 
      case ENoFou: name = "ENoFou", description="Error: Not found"; break; 
      case EWroTy: name = "EWroTy", description="Error: Wrong type"; break; 
      case EWroVa: name = "EWroVa", description="Error: Wrong value, setter rejected"; break; 
      // Do not use default to receive a comile-time warning about not all values of enumeration are handled. Need `-Wswitch-enum` in GCC 
     } 
    } 
}; 

Mở rộng ví dụ này để một số mã phổ biến, chúng ta hãy đặt tên cho nó đang lib:

/// Concept of Compile time meta information about (enum) value. 
/// This is the best implementation because: 
/// - compile-time resolvable using `constexpr name = CtMetaInfo<T>(value)` 
/// - enum type can be implemented anywhere 
/// - compile-time check for errors, no hidden run-time surprises allowed - guarantied by design. 
/// - with GCC's `-Wswitch` or `-Werror=switch` - you will never forget to handle every enumerated value 
/// - nice and simple syntaxis `CtMetaInfo(enumValue).name` 
/// - designed for enums suitable for everything 
/// - no dependencies 
/// Recommendations: 
/// - write `using TypeToString = CtMetaInfo<Type>;` to simplify code reading 
/// - always check constexpr functions assigning their return results to constexpr values 
/**\code 

    template< typename T > 
    concept CtMetaInfo_CONCEPT { 
     T value; 
     const char *name; 
     // Here could add variables for any other meta information. All vars must be filled either by default values or in ctor init list 
    public: 
     ///\param defaultName will be stored to `name` if constructor body will not reassign it 
     constexpr CtMetaInfoBase(T const& val, const char* defaultName="Unknown"); 
    }; 

    \endcode 
*/ 

/// Pre-declare struct template. Specializations must be defined. 
template< typename T > 
struct CtMetaInfo; 

/// Template specialization gives flexibility, i.e. to define such function templates 
template <typename T> 
constexpr const char* GetNameOfValue(T const& ty) 
{ return CtMetaInfo<T>(ty).name; } 

dùng phải xác định chuyên môn cho loại hình của người dùng:

/// Specialization for `rapidjson::Type` 
template<> 
struct CtMetaInfo<Type> { 
    using T = Type; 
    T value; 
    const char *name; 
public: 
    constexpr CtMetaInfo(T const& val) 
      : value(val) 
      , name("Unknown") 
    { 
     switch(val){ 
      case kNullType     : name = "Null" ; break; 
      case kFalseType: case kTrueType: name = "Bool" ; break; 
      case kObjectType    : name = "Object"; break; 
      case kArrayType    : name = "Array" ; break; 
      case kStringType    : name = "String"; break; 
      case kNumberType    : name = "Number"; break; 
     } 
    } 
    static constexpr const char* Name(T const& val){ 
     return CtMetaInfo<Type>(val).name; 
    } 
}; 

/// TEST 
constexpr const char *test_constexprvalue = CtMetaInfo<Type>(kStringType).name; 
constexpr const char *test_constexprvalu2 = GetNameOfValue(kNullType); 
Các vấn đề liên quan