2015-05-07 33 views
6

Çeşitli potansiyel olarak pahalı yöntemlerin sonucunu dikkate alması gereken bir karşılaştırıcı tanımlamanız gereken bir C++ sınıfına sahibim. Bu yöntemlerin sonucunu setimdeki tüm nesneler için önbelleğe almak istemiyorum, çünkü en yüksek önceliğe sahip kriterler daha ucuzdur ve altta çok pahalı olanların yalnızca nadir durumlarda tetiklenmesini beklerim.C++ zarif sözdizimi ile derin temsili karşılaştırma?

İlk bağımsız değişken, daha az, eşit veya daha büyük olduğunda, -1, 0 veya 1 döndüren bir cmp() işlevine sahip olsaydım ve tamsayıları koruyan kısayol mantıksal işleçler ile kolayca yazabilirim

int compare(const Class &rhs) const { 
    return cmp(expensive_method_a(), rhs.expensive_method_b()) || 
      cmp(expensive_method_b(), rhs.expensive_method_b()) || 
      ... 
} 

Ne yazık ki < operatör ile çalışmak gerekir, bu yüzden, çirkin masraflı ve hataya açık hale gelir: alternatif olarak daha az maliyetli ama hala oldukça çirkin

bool operator<(const Class &rhs) const { 
    return expensive_method_a() < rhs.expensive_method_a() || 
      (expensive_method_a() == rhs.expensive_method_a() && 
      (expensive_method_b() < rhs.expensive_method_b() || 
      (expensive_method_b() == rhs.expensive_method_b() && 
       (... 
      )))) 
} 

Veya:

Std :: tie hakkında This başka bir soru okudum, ancak doğru bir şekilde anlarsam, kravat karşılaştırmaya başlamadan önce tüm yöntemlerimi değerlendirirdi ve bu argümanların tembel bir şekilde değerlendirilmesini istiyorum. ,

bool operator<(const Class &rhs) const { 
    CUT_COMPARE(expensive_method_a(), rhs.expensive_method_a()); 
    CUT_COMPARE(expensive_method_b(), rhs.expensive_method_b()); 
    ... 
} 

parantez özel kapsam benim _x ve _y içine umarak: Ben gibi kullanmak

#define CUT_COMPARE(a,b) { auto _x = (a); auto _y = (b); if (_x != _y) return (_x < _y); } 

:

Ben bunun gibi bir önişlemci makro tanımlama hakkında düşünce Ancak, clang++ numaralı belge, _x ve _y'un çoklu tanımlarından şikayetçidir.

Bu konuda daha güzel bir yol var mı?

+2

Clang neden şikayet ediyor? Kodu test etmedim, ancak değişkenlerin ayrı kapsamlarda olduğu görülüyor. –

+1

Sadece bir not olarak, 'CUT_COMPARE' aslında burada yazılan nedenlerden dolayı #define CUT_COMPARE (a, b) {....} yazmalı (http) yazılmalıdır: http://stackoverflow.com/questions/ 1067226/c-çok satırlı makro-do-while0-vs-kapsam bloğu –

cevap

4

Sen içlerinden gerektiği gibi yürür bir yardımcı şablonuna aramak istediğiniz tüm üye işlevlerini iletebilirsiniz lazy_compare variadic işlevi, gerektiğinde işaretçi-üye işlevlerini birer birer yürütebilir.

template <typename T, typename... MFs> 
bool lazy_compare(const T&, const T&, MFs...) { 
    return true; 
} 

Ve özyinelemeli vaka birinci işaretçi-to-üyesini kapalı pop ve biz şuna engelleyebilirsen görmektir: temel durum sadece true olduğunu

template <typename T, typename R, typename... MFs> 
bool lazy_compare(const T& left, const T& right, R (T::*mf)() const, MFs... rest) { 
    R vleft = (left.*mf)(), vright = (right.*mf)(); 
    if (vleft != vright) { 
     return vleft < vright; 
    } 
    else { 
     return lazy_compare(left, right, rest...); 
    } 
} 
+0

Güzel! Belki de 'değerlendirme' den daha iyi bir isim seçin ... –

+0

@LightnessRacesinOrbit Ben şeyleri adlandırma emmek. Öneriye açık mıyım? – Barry

+0

için @Barrry: "RecursiveLessThanComparator" ile "lazy_compare" ile gittim, ancak net değil. Dunno :) –

0

yazdım, yöntemini karşılaştırmak bir -güzel bu şekilde sadık kalacağına:

int compare(const Class &rhs) const { 
    int cr; 
    cr = cmp(expensive_method_a(), rhs.expensive_method_a()); 
    if (cr != 0) return cr; 
    cr = cmp(expensive_method_b(), rhs.expensive_method_b()); 
    if (cr != 0) return cr; 
    ... 
} 

bir yöntem farklı bir sonuç verir ve sadece durumda sonuna kadar iner en kısa sürede doğru işareti ile döndürür Bu şekilde eşitlik

Ve tüm Karşılaştırıcıların doğrudan kullanabilirsiniz:

bool operator<(const Class& rhs) const { 
    return lazy_compare(*this, rhs, &Class::expensive_1, 
            &Class::expensive_2, 
            &Class::expensive_3); 
} 

:

bool operator<(const Class &rhs) const { 
    return compare(rhs) < 0; 
} 
bool operator<=(const Class &rhs) const { 
    return compare(rhs) <= 0; 
} 
bool operator>(const Class &rhs) const { 
    return compare(rhs) > 0; 
} 
bool operator>=(const Class &rhs) const { 
    return compare(rhs) >= 0; 
} 
bool operator==(const Class &rhs) const { 
    return compare(rhs) == 0; 
} 
bool operator!=(const Class &rhs) const { 
    return compare(rhs) != 0; 
} 
+0

Yaptığınız tek şey, “işleç” gövdesini “karşılaştır” işlevinin gövdesine taşıyordu. –

+0

@LightnessRacesinOrbit: evet, ancak tüm karşılaştırıcılar için kullanılabileceğinden, ağır kısmın tek bir yerde olduğu ve tekrar eden bölümün önemsiz olduğu için (en azından kısmen) soruyu yanıtladığını düşünüyorum. Benim düzenleme görmek –

-4

sadece uygulayabilirsiniz Bu gibi:

bool operator<(const Class &rhs) const { 
    return expensive_method_a() < rhs.expensive_method_a() || 
      expensive_method_b() < rhs.expensive_method_b() || 
      .. 
      expensive_method_N() < rhs.expensive_method_N() || 
} 

diğer olanları değerlendirilmeden, en kısa sürede yöntemlerden biri doğru olarak değerlendirilir olarak dönecektir.

+0

olduğu bir yerde başka bir aşırı yükleme daha ekleyin. İlk karşılaştırma aynı değerde sonuçlanırsa, yalnızca 'expensive_method_b' yi test etmeliyiz . Eğer cost_method_a()> rhs.expensive_method_a() 'ise, hemen false döndürürüz. –

+3

Bu, istenen davranışla aynı anlamlara sahip değil. Eğer bu -> a()> rhs.a() 'ancak' bu-> b() Barry

2

İşte tembel bir karşılaştırma nesnesi. Bazı keyfi çağrılabilir F tutan ve lazy_comp_f<?> nesnelerin bir çift cmp(lhs, rhs) çağırdığınızda onu çağırır, sonuçlarını saklar ve kazanan kim söyler: Burada

template<class F> 
struct lazy_comp_f { 
    F f; 
    template<class F1, class F2> 
    friend int cmp(lazy_comp_f<F1>const& lhs, lazy_comp_f<F2>const& rhs) { 
    auto l = lhs.f(); 
    auto r = rhs.f(); 
    // using cmp_ns::cmp; here 
    return cmp(l,r); 
    } 

    // ctors 
    lazy_comp_f(F&& fin):f(std::forward<F>(fin)) {} 
    lazy_comp_f(lazy_comp_f&&)=default; 
    lazy_comp_f(lazy_comp_f const&)=default; 
    template<class O, class=std::enable_if_t<std::is_convertible<O const&,F>>> 
    lazy_comp_f(lazy_comp_f<O> const&o):f(o.f){} 
    template<class O, class=std::enable_if_t<std::is_convertible<O,F>>> 
    lazy_comp_f(lazy_comp_f<O>&&o):f(std::move(o).f){} 
}; 
template<class T> 
using lazy_comp_t = lazy_comp_f<std::function<T()>>; 

indirimine yapan bir şablon fabrika işlevi yardımcısıdır F türü: Burada

template<class F> 
lazy_comp_f<std::decay_t<F>> 
lazy_comp(F&& f){ return {std::forward<F>(f)}; } 

tembel kravat.

template<class...Fs, class R=std::tuple< lazy_comp_f<std::decay_t<Fs>>... >> 
R lazy_tie(Fs&& fs) { 
    return R(lazy_comp(std::forward<Fs>(fs)...)); 
} 

İşte bizim temel cmp geçerli: Pahalı ürün üretmek için kullanılan fonksiyonları bir dizi alır. < kullanır ve makul bir cmp işlemi üretir. Şimdi dizilerini cmp izin için çaba

template<class T, class U> 
int cmp(T const& lhs, U const& rhs) { 
    if (lhs < rhs) return -1; 
    if (rhs < lhs) return 1; 
    return 0; 
} 

: Yerel ADL arama daha iyi yapabiliriz durumlar için daha iyi bir aşırı bulabilirsiniz. İki yardımcıları:

namespace details { 
    template<class...Ts, class...Us> 
    int cmp(
    std::index_sequence<>, 
    std::tuple<Ts...> const& lhs, 
    std::tuple<Us...> const& rhs 
) { 
    return 0; 
    } 
    template<size_t I, size_t...Is,class...Ts, class...Us> 
    int cmp(
    std::index_sequence<I, Is...>, 
    std::tuple<Ts...> const& lhs, 
    std::tuple<Us...> const& rhs 
) { 
    // maybe using comp_ns::cmp here? 
    int c = cmp(std::get<I>(lhs), std::get<I>(rhs)); 
    if (c!=0) return c; 
    return cmp(std::index_sequence<Is...>{}, lhs, rhs); 
    } 
} 

ve biz lhs eşsiz sayıda/karşı savunma ile yardımcı diyoruz rhs'si args:

template<class...Ts, class...Us> 
std::enable_if_t<sizeof...(Ts)==sizeof...(Us), int> 
cmp(
    std::tuple<Ts...> const& lhs, 
    std::tuple<Us...> const& rhs 
) { 
    return details::cmp(std::make_index_sequence<sizeof...(Ts)>{}, lhs, rhs); 
} 

şimdi sorun sadece callables sağlamaktır! class İçinde

aşağıdakileri yapın: Biz

auto lazy_comparer() const 
// std::tuple< lazy_comp_t<A>, lazy_comp_t<B>, lazy_comp_t<C> > in C++11 
// where `A`, `B` and `C` are the return types of expensive_method_a etc 
{ 
    return lazy_tie(
    [=]{ return expensive_method_a(); }, 
    [=]{ return expensive_method_b(); }, 
    [=]{ return expensive_method_c(); } 
    // etc 
); 
} 
friend int cmp(Class const& lhs, Class const& rhs) { 
    // using namespace cmp_ns::cmp here 
    return cmp(lhs.lazy_comparer(), rhs.lazy_comparer()) < 0; 
} 
friend bool operator<(Class const& lhs, Class const& rhs) { 
    return cmp(lhs,rhs)<0; 
} 

ve bitti mi?

Bu çözümün yinelemeli çalıştığını unutmayın. cmp'u geçersiz kılan herkes, en uygun sürüme sahip olur ve hiç kimse, < temel alınmayan birini temel alır. Bazı alt yapı kendi lazy tabanlı cmp ise, çağrılır.

C++ 14'te bu, düşük tip silme yükü ile yapılır. C++ 11'de, bazı anlamsız tahsisler (tip silme için) yapılır - delege benzeri bir yaklaşımla (hafif std::function s) veya diğer mikro-optimizasyonlarla daha hızlı yapılabilir.

Bazı C++ 14 özellikleri kullanıldı. C++ 11'de (bir geçici çözüm sağladığım auto dönüş türü dışında) uygulanması kolaydır.

+0

Bu gerçekten etkileyici ve aydınlatıcı ve keşke iki cevabı kabul etmeyi dilerim. Diğeri için gidiyorum, çünkü sonunda, anlaşılması çok daha kolaydı, ama yazma vaktini aldığın için çok teşekkürler, hala davranışını tam olarak anlamak için biraz daha çalışmam gerekiyor. – b0fh