Как базовый класс вызывает замыкание, переданное производным классом в c ++? - PullRequest
0 голосов
/ 14 мая 2018

У меня есть базовый класс, и у него есть функция-член, которая когда-нибудь будет вызвана.Обычно эта функция имеет параметр, указывающий на себя.

class Base {

public:
    std::function<bool(Base *, int)> foo;

private:
    int x{};

public:
    static std::shared_ptr<Base> create() {
        return std::make_shared<Base>();
    }

    Base() = default;

    const std::function<bool(Base *, int)> &getFoo() const {
        return foo;
    }

    void setFoo(const std::function<bool(Base *, int)> &foo) {
        Base::foo = foo;
    }

    int getX() const {
        return x;
    }

    void setX(int x) {
        Base::x = x;
    }
};

Но когда у меня есть производный класс, как я могу установить эту функцию-член?Хотя указатель базового класса может указывать на объект подкласса, но я непосредственно передал в производный объект, компилятор не передает.

class Derived : public Base {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }
};

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Derived *derived, int x) -> bool { return derived->getX() > x; });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

ошибка: нет жизнеспособного преобразования из '(лямбда в main.cpp: 62: 15)' в 'const std :: function' b-> setFoo ([] (производная * получена), int x) -> bool {return output-> getX ()> x;});^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Итак, есть ли хорошая идея передать замыкание в базовый класс, и базовый класс вызывает его вместо производного класса, и самое главное, у замыкания есть параметр, который указывает на то, кто передает замыкание!

Ответы [ 3 ]

0 голосов
/ 14 мая 2018

Примечание

Я собираюсь предположить, что по какой-то причине рассматриваемое закрытие нуждается в доступе к методам / элементам данных Derived, и пример OP не очень хорошо передает это.Иначе, почему бы просто не использовать Base * в качестве входного параметра:

b->setFoo([](Base *derived, int x) -> bool { return derived->getX() > x; });

@ user3655463 ответ содержит полный код для этого случая.

Простое решение

В случаеCRTP-решение, предложенное @Yuki, не работает для вас, вы можете просто использовать Base * в качестве аргумента для замыкания и static_cast его в теле замыкания (компилятор может оптимизировать преобразование), например:

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Base *derived, int x) -> bool {
        return static_cast<Derived *>(derived)->getX() > x;
    });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

Живой пример .

Если вам действительно нужно, чтобы тип в замыкании был Derived *

В случае наличия Base * взакрытие недопустимо, вы можете скрыть метод setFoo от Base со специальной реализацией в Derived, которая выполнит приведение за вас:

class Derived : public Base {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }

    template <typename Closure>
    void setFoo(Closure foo) {
        Base::setFoo([foo](Base *base, int x) {
            return foo(static_cast<Derived *>(base), x);
        });
    }
};


int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Derived *derived, int x) -> bool {
        return derived->getX() > x;
    });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

Это позволяет вам использовать тот же интерфейскак у вас в исходной основной функции.

Живой пример .

Если у вас много производных классов, и вы не хотите скрывать этот метод снова иснова в каждом классе

Теперь все становится немного сложнее, и нОтметим, что для вас это хороший шанс сделать что-то подобное, в вашем случае это будет слишком трудоемким, но я просто хочу продемонстрировать, что это возможно - вот где CRTP вступает в игру.Он используется для реализации миксина, который обеспечивает реализацию метода setFoo:

template <typename ConcreteDerived, typename DirectBase>
class EnableSetFooAndInherit : public DirectBase {
public:
    template <typename Closure>
    void setFoo(Closure foo) {
        DirectBase::setFoo([foo](DirectBase *base, int x) {
            return foo(static_cast<ConcreteDerived *>(base), x);
        });
    }
};

class Derived : public EnableSetFooAndInherit<Derived, Base> {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }
};

class Derived2 : public EnableSetFooAndInherit<Derived2, Base> {
public:
    static std::shared_ptr<Derived2> create() {
        return std::make_shared<Derived2>();
    }
};

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Derived *derived, int x) -> bool {
        return derived->getX() > x;
    });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    auto d2 = Derived2::create();
    d2->setX(77);
    d2->setFoo([](Derived2 *derived, int x) -> bool {
        return derived->getX() < x;
    });

    if (d2->getFoo()) {
        auto res = d2->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

Живой пример .

0 голосов
/ 14 мая 2018

Разве вы не можете просто использовать Base (как вы и планировали):

d->setFoo([](Base* derived, int x) -> bool { return derived->getX() > x; });

Весь код:

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>
#include <memory>

class Base {

public:
    std::function<bool(Base *, int)> foo;

private:
    int x{};

public:
    static std::shared_ptr<Base> create() {
        return std::make_shared<Base>();
    }

    Base() = default;

    const std::function<bool(Base *, int)> &getFoo() const {
        return foo;
    }

    void setFoo(const std::function<bool(Base *, int)> &foo) {
        Base::foo = foo;
    }

    int getX() const {
        return x;
    }

    void setX(int x) {
        Base::x = x;
    }

};

class Derived : public Base {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }
};

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Base* derived, int x) -> bool { return derived->getX() > x; });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}
0 голосов
/ 14 мая 2018

Если базовое решение шаблона соответствует вашему стилю, это может сработать.

template <typename D>
class Base {

public:
  std::function<bool(D*, int)> foo;

private:
  int x{};

public:
  static std::shared_ptr<Base> create() { return std::make_shared<Base>(); }

  Base() = default;

  const std::function<bool(D*, int)>& getFoo() const { return foo; }

  void setFoo(const std::function<bool(D*, int)>& foo) { Base::foo = foo; }

  int getX() const { return x; }

  void setX(int x) { Base::x = x; }
};

class Derived : public Base<Derived> {
public:
  static std::shared_ptr<Derived> create() { return std::make_shared<Derived>(); }
};

int main() {
  auto d = Derived::create();
  d->setX(77);
  d->setFoo([](Derived* derived, int x) -> bool { return derived->getX() > x; });

  if (d->getFoo()) {
    auto res = d->foo(d.get(), 99);
    std::cout << res << std::endl;
  }

  return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...