30 listopada 2013

[C++11] Wskaźniki na metody i pola klasy

Wskaźniki na funkcje, a co dopiero na metody klas zawsze sprawiały mi problemy. Nowy standard oferuje kilka narzędzi, dzięki którym praca z nimi staje się trochę łatwiejsza. Dużo fajnych opisów znalazłem pod poniższym linkiem.
Możemy stworzyć wskaźnik na pola lub metody, korzystając z auto bądź decltype, zamiast pisać skomplikowaną deklarację (w starym stylu). Analogicznie do operatorów odwołujących się do zmiennych obiektu (. oraz ->) istnieją dwa mechanizmy do odwołania się do pola (zmiennej lub metody) przez wskaźnik: .* oraz ->*.

Poniżej ptr_name, jest wskaźnikiem na pole MyClass::name. Dla ptr_name2 skorzystałem z auto dzięki czemu deklaracja znacznie się skróciła.
#include <iostream>
using namespace std;

struct MyClass {
    std::string name;
};

int main() {
    MyClass m;

    std::string MyClass::*ptr_name = &MyClass::name;

    m.name = "tekst";
    MyClass *d = &m;
    cout << d->*ptr_name << endl;
    cout << m.*ptr_name << endl;

    m.*ptr_name = "blow";
    cout << m.name << endl;

    auto ptr_name2 = &MyClass::name;
    m.*ptr_name2 = "blow up";
    cout << m.name << endl;

    return 0;
}
Wynik:
tekst
tekst
blow
blow up

Wskaźnik do metody w klasie

Na początek prosty przykład z wołaniem metody bezargumentowej. Standard wprowadza dodatkowe trzy mechanizmy pozwalające na odwołanie się to metody klasy. Są to std::function (klasa szablonowa), std::mem_fn oraz std::bind pozwalająca na wygenerowanie wołanego obiektu ze wskaźnika na funkcję. Ponieważ działam na metodach klasy spodziewam się, że std::mem_fn jest do tego najlepszym mechanizmem (z racji przeznaczenia), ale nie wiem, czy jest jakaś istotna różnica w porównaniu do dwóch pozostałych.
#include <algorithm>
#include <functional>
#include <iostream>

struct MyClass {
    MyClass(std::string s) : name(s) { }
    std::string info() {
        std::cout << "info " << name << std::endl;
        return name;
    }
    int fun(char*) {
        std::cout << "fun " << name << std::endl;
        return 8;
    }

    std::string name;
};

int main() {
    using namespace std::placeholders;
    std::vector<MyClass> vec {MyClass("a"), MyClass("b"), MyClass("c")};

    std::function<std::string (MyClass&)> pinfo1 = &MyClass::info;
    std::for_each(begin(vec), end(vec), pinfo1);

    std::for_each(begin(vec), end(vec), std::mem_fn(&MyClass::info));

    auto pinfo2 = std::bind(&MyClass::info, _1);
    std::for_each(begin(vec), end(vec), pinfo2);

    return 0;
}
Następny przykład to wołaniem metody (MyClass::fun) posiadającej jeden argument. Na początku deklaracja wskaźnika na funkcję w starym dobrym stylu (pfun1) i jej uproszczona wersja korzystająca z auto (pfun2). Korzystanie z std::function oraz std::mem_fn jest już trochę bardziej skomplikowane i wymaga zastosowania lambdy (nie jestem pewien, czy można sobie to jeszcze jakoś bardziej ułatwić).

Najbardziej przejrzyste rozwiązanie powstało z std::bind dzięki placeholder-om. Warto pamiętać, że korzystając ze wskaźników na funkcję (tak przy deklaracji jak i wywołaniu), należy użyć dodatkowych nawiasów, ponieważ operator nawiasowy ma wyższy priorytet niż operator wskaźnika-na-pole (.*). Ogólne postać:
(Class::*fun)(param) (obj.*fun)(arg)
Pełny przykład:
#include <algorithm>
#include <functional>
#include <iostream>

struct MyClass {
    MyClass(std::string s) : name(s) { }
    std::string info() {
        std::cout << "info " << name << std::endl;
        return name;
    }
    int fun(char*) {
        std::cout << "fun " << name << std::endl;
        return 8;
    }

    std::string name;
};

int main() {
    using namespace std::placeholders;
    std::vector<MyClass> vec {MyClass("a"), MyClass("b"), MyClass("c")};

    int (MyClass::*pfun1)(char*) = &MyClass::fun;
    std::for_each(begin(vec), end(vec), [&](MyClass& c){ (c.*pfun1)(nullptr); });

    auto pfun2 = &MyClass::fun;
    std::for_each(begin(vec), end(vec), [&](MyClass& c){ (c.*pfun2)(nullptr); });

    std::function<int(MyClass&, char*)> pfun3 = &MyClass::fun;
    std::for_each(begin(vec), end(vec), [&](MyClass& c){ pfun3(c, nullptr); });

    std::for_each(begin(vec), end(vec),
                  [&](MyClass& c){ std::mem_fn(&MyClass::fun)(c, nullptr); });

    std::for_each(begin(vec), end(vec), std::bind(&MyClass::fun, _1, nullptr));

    return 0;
}
A teraz problem, który od dawna chodził mi po głowie, czyli stworzenie funkcji hash-ującej dla unordered_map, bez wrapera w postaci free function. Ponieważ getHash() jest metodą const-ową, std::function musi to uwzględniać w parametrze.
#include <functional>
#include <iostream>
#include <unordered_map>

struct Color {
    Color(size_t v) : value(v) {}
    size_t getHash() const {
        std::cout << "getHash() " << value << std::endl;
        return value;
    }
private:
    size_t value;
};

int main() {
    using namespace std::placeholders;
    constexpr size_t bucket_count = 42;

    auto equalOp = [] (const Color& l, const Color& r)
                    { return l.getHash() == r.getHash(); };

    std::function<size_t(const Color&)> hashFun1 = &Color::getHash;
    std::unordered_map<Color,
            std::string,
            decltype(hashFun1),
            decltype(equalOp)> m1(bucket_count, hashFun1, equalOp);

    m1[Color(1)] = "blue";

    auto hashFun2 = std::mem_fn(&Color::getHash);
    std::unordered_map<Color,
            std::string,
            decltype(hashFun2),
            decltype(equalOp)> m2(bucket_count, hashFun2, equalOp);

    m2[Color(2)] = "red";

    auto hashFun3 = std::bind(&Color::getHash, _1);
    std::unordered_map<Color,
            std::string,
            decltype(hashFun3),
            decltype(equalOp)> m3(bucket_count, hashFun3, equalOp);

    m3[Color(3)] = "green";

    return 0;
}

Brak komentarzy:

Prześlij komentarz