2017-08-22 62 views
7

Tôi có một quá trình băm được thực hiện bằng cách sử dụng Howard Hinnant's method (băm chung dựa trên quá tải hash_append).Loại bỏ đa hình theo cách thích hợp

Mục đích của phương pháp đó là tạo băm lớp học để "ghi nhớ" kết quả tính toán (xem kết thúc câu trả lời này), vì vậy tôi đang gặp phải một số vấn đề. Đặc biệt, xem xét các lớp có thể Input sau đó cần phải được băm:

struct A { 
    virtual int do_stuff() const = 0; 
    virtual ~A(); 
}; 
struct B: A { 
    int do_stuff() const override { return 0; } 
}; 
struct C: A { 
    const int u; 
    int do_stuff() const override { return u; } 
}; 

struct Input { 
    A const& a; // store a reference to an instance of B or C 
}; 

Bây giờ, nếu tôi muốn băm Input, tôi sẽ có một cái gì đó như:

template <class HashAlgorithm> 
void hash_append(HashAlgorithm& h, Input const& input) { 
    hash_append(h, typeid(input)); 
    hash_append(h, typeid(input.a)); 
} 

Vì vậy, tôi cần một tình trạng quá tải của hash_append cho A:

template <class HashAlgorithm> 
void hash_append(HashAlgorithm& h, A const& a) { 
    hash_append(h, typeid(a)); 
} 

vấn đề ở đây là tùy thuộc vào loại thời gian chạy của a, tôi sẽ cần thêm thông tin bổ sung vào hàm băm, ví dụ: cho C Tôi cần phải thêm u.

Tôi nghĩ về các giải pháp sau (và nhược điểm):

  1. thêm một phương pháp ảo để A mà trả về một giá trị cụ thể có thể được bổ sung vào typeid() băm, nhưng:

    • điều này có nghĩa là thêm một phương thức bên trong A không liên quan đến mục đích của A, do đó tôi không thực sự thích ý tưởng này (đặc biệt vì tôi có nhiều lớp giống như A);
    • điều này sẽ phá vỡ khái niệm hash_append vì phương thức này sẽ có kiểu trả về duy nhất cho tất cả các lớp kế thừa.
  2. làm một loạt các dynamic_cast bên hash_append:

    • Tôi thấy điều này khá xấu xí ... đặc biệt là nếu tôi có nhiều lớp học tương tự như A;
    • điều này là dễ xảy ra lỗi: nếu ai đó thêm một trẻ em mới là A và không thêm dynamic_cast vào bên trong hash_append.

Có cách nào để băm một loại đa hình, mà không cần phải sửa đổi các loại bản thân hoặc dựa trên một loạt các dynamic_cast?


Mục tiêu cuối cùng là có thể ghi nhớ kết quả của một số chức năng nặng. Hãy phác thảo cấu trúc cơ bản của ứng dụng của tôi:

struct Input { }; 
struct Result { }; 

Result solve(Input const&); 

Chức năng solve là tính toán nặng, vì vậy tôi muốn lưu các kết quả tính toán trước đó trong tập tin sử dụng hash của Input s, ví dụcái gì đó như:

// depends on hash_append 
std::string hash(Input const&); 

Result load_or_solve(Input const& input) { 
    auto h = hash(input); 
    Result result; 
    if (exists(h)) { // if result exists, load it 
     result = load(h); 
    } 
    else { // otherwize, solve + store 
     result = solve(input); 
     store(h, result); 
    } 
    return result; 
} 

Các loadstore phương pháp này sẽ tải và lưu trữ kết quả từ các tập tin, mục đích là để memoize giải pháp giữa chạy khác nhau.

Nếu bạn có đề xuất về cách ghi nhớ các kết quả này mà không phải giải quyết các vấn đề trên, tôi sẽ rất vui khi đọc chúng.

+1

Bạn có thể sử dụng điều độ đôi trong 'phiên bản hash_append' của 'A' và gửi yêu cầu đến các phiên bản thích hợp cho' b' và 'C 'khi bạn lấy lại loại, nhưng tôi không nghĩ đó chính là thứ bạn đang tìm kiếm. Bạn đã xem nó chưa? Hạn chế là bạn phải thêm boilerplate vào các lớp đó để chấp nhận khách truy cập. Nếu nó có thể làm việc cho bạn, tôi có thể đưa bình luận vào một câu trả lời chi tiết hơn. – skypjack

+0

@skypjack Tôi xin lỗi vì tôi không hoàn toàn hiểu ý của bạn - bạn có thể viết một ví dụ nhỏ để minh họa ý nghĩa của bạn không? – Holt

+1

Tôi có nghĩa là một cái gì đó [dọc theo dòng này] (http://coliru.stacked-crooked.com/a/c1b5c249535f7590). Đó không phải là một ví dụ làm việc nhưng bạn nên có ý tưởng. Thật không may mã ví dụ của bạn thiếu rất nhiều mã để có thể đóng gói một ví dụ cụ thể, tôi xin lỗi. Và dĩ nhiên, 'HashVisitor' có thể được _simplified_ (ít nhất, được thiết kế để bạn không phải sửa đổi nó mỗi khi bạn định nghĩa một kiểu mới) bằng cách sử dụng các mẫu variadic và kế thừa trực tiếp, nhưng cách tôi định nghĩa nó sẽ dễ dàng hơn hiểu. – skypjack

Trả lời

4

Bạn có thể sử dụng phép gửi kép trong phiên bản hash_append của A và chuyển tiếp yêu cầu đến phiên bản thích hợp (đó là phiên bản B hoặc C). Hạn chế là bạn phải thêm boilerplate vào các lớp đó để chấp nhận một khách truy cập và tôi không thể nói nếu nó có thể chấp nhận được cho bạn.
Đây là một loạt các mã mà nên minh họa cho ý tưởng:

struct B; 
struct C; 

struct Visitor { 
    virtual void visit(const B &) = 0; 
    virtual void visit(const C &) = 0; 
}; 

template<typename T, typename... O> 
struct HashVisitor: T, HashVisitor<O...> { 
    template<typename U> 
    std::enable_if_t<std::is_same<T, U>::value> tryVisit(const U &u) { 
     T::operator()(u); 
    } 

    template<typename U> 
    std::enable_if_t<not std::is_same<T, U>::value> tryVisit(const U &u) { 
     HashVisitor<O...>::visit(u); 
    } 

    void visit(const B &b) override { tryVisit<B>(b); } 
    void visit(const C &c) override { tryVisit<C>(c); } 
}; 

template<> 
struct HashVisitor<>: Visitor {}; 

template<typename... F 
auto factory(F&&... f) { 
    return HashVisitor<std::decay_t<F>>{std::forward<F>(f)...}; 
} 

struct A { 
    virtual void accept(Visitor &) = 0; 
    virtual int do_stuff() const = 0; 
    virtual ~A(); 
}; 

struct B: A { 
    void accept(Visitor &v) override { v.visit(*this); } 
    int do_stuff() const override { return 0; } 
}; 

struct C: A { 
    const int u; 
    void accept(Visitor &v) override { v.visit(*this); } 
    int do_stuff() const override { return u; } 
}; 

template <class HashAlgorithm> 
void hash_append(HashAlgorithm &, const B &) { 
    // do something 
} 

template <class HashAlgorithm> 
void hash_append(HashAlgorithm &, const C &) { 
    // do something 
} 

template <class HashAlgorithm> 
void hash_append(HashAlgorithm &h, const A &a) { 
    auto vis = factory(
     [&h](const B &b){ hash_append(h, b); }, 
     [&h](const C &c){ hash_append(h, c); } 
    ); 

    a.accept(vis); 
} 
+0

Cảm ơn câu trả lời - Tôi nghĩ đây là câu trả lời hay nhất nếu tôi muốn có sự vững mạnh, tức là không có loại với phương thức băm không được triển khai. Thật không may, tôi phải đi theo hướng ngược lại, tức là một phương pháp ít mạnh mẽ hơn (tại thời gian biên dịch) nhưng với ít ràng buộc hơn: thông thường, tôi không thể chuyển tiếp các lớp trước khách truy cập, và tôi vẫn muốn tách quá trình băm từ tự học. Tôi đang cố gắng để thực hiện một cái gì đó mà sẽ cho phép tôi thêm công văn thời gian chạy tùy chỉnh sau khi tuyên bố các lớp học (Tôi là một chút bị mắc kẹt vào thời điểm đó, có thể mở một câu hỏi sớm ...). – Holt

+0

Hãy cho tôi biết và tôi sẽ cố gắng trả lời bạn nếu có thể. – skypjack

+0

Nó ở đây - https://stackoverflow.com/questions/45837117/storing-various-tuples-and-applying-templated-functor-on-them – Holt

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