Как обернуть и перегрузить все функции-члены (из неизвестного класса) во время компиляции, используя метапрограммирование c ++? - PullRequest
0 голосов
/ 04 апреля 2020

Допустим, у нас есть class A (который мы не можем изменять) и мы хотим обернуть все функции-члены новым связующим кодом для определения поведения.

struct A {
    int foo(){ return a; }
    int bar(){ return b; }
    int a = 1;
    int b = 2;
}

Как мы можем обернуть любой класс T (который может иметь несколько методов) для вызова в T, только если выполняются условия (ie. Указатель не равен нулю).

template<class T>
struct Wrapped {
    Wrapped(T* t): _t(t){ }
    int foo(){ return (_t == nullptr ? 0 : _t->foo()); }
    int bar(){ return (_t == nullptr ? 0 : _t->bar()); }
    T* _t;
}

Вариант использования заключается в том, что мы можем манипулировать Wrapped<A>, как если бы он был A безопасно:

auto a1 = A();
a1.foo(); // 1

auto a2 = Wrapped<A>(new A());
a2.foo(); // 1

auto a3 = Wrapped<A>(nullptr);
a3.foo(); // 0

Другие примечания:

  • Мы бы не хотели писать новый специализированный Wrapper для каждого возможного класса, который можно обернуть.
  • Я знаю, что такие вещи, как shared_ptr<>, существуют, но я пытаясь вставить пользовательское поведение между доступом к классу и самим вызовом метода .
  • Я хотел бы, чтобы это было как облегченный с точки зрения производительности.
  • Если возможно, нет большой зависимости (ie. Boost)

Любые предложения или указания в правильное направление приветствуется!

Ответы [ 4 ]

2 голосов
/ 04 апреля 2020

C ++ в настоящее время не хватает необходимого механизма отражения для реализации чего-то подобного, удовлетворяющего всем вашим требованиям.

Нет способа получить список имен функций-членов класса и единственный способ «переадресации» члена вызовы функций без знания имен функций-членов operator->, как это делают умные указатели стандартной библиотеки, что, по-видимому, вам не кажется достаточным для ваших целей.

Вам нужно либо изменить сами исходные классы, чтобы обеспечить отражение или вам нужно написать обертки для каждого класса индивидуально (или хотя бы перечислить все возможные имена функций-членов).

В настоящее время существует черновик для Reflection TS (Техническая спецификация, т.е. экспериментальное расширение для C ++), которая позволил бы отразить и получить имена функций-членов, но все равно не позволил бы использовать эти отраженные имена для объявления сущностей с этими именами из того, что я могу сказать, и, следовательно, все еще не достаточно для решения проблемы. ваш вариант использования.

0 голосов
/ 10 апреля 2020

Вы можете рассмотреть возможность использования необязательного значения с std::optional<T>:

const A defaultA{ 0, 0 };   // set the default values that must be returned

std::optional<A> foo(int x)
{
    auto opt = std::optional<A>();
    if (x >= 0)
        opt.emplace();
    return opt;
}

int main()
{
    auto a1 = foo(-1);      // returns an optional with no value
    std::cout << a1.value_or(defaultA).foo() << std::endl; // 0 (foo is called on defaultA)
    std::cout << (a1?a1->bar():0)            << std::endl; // 0

    auto a2 = foo(4);       // returns an optional with a value
    std::cout << a2.value_or(defaultA).foo() << std::endl; // 1 
    std::cout << a2.value_or(defaultA).bar() << std::endl; // 2
}

Для получения дополнительной информации о std::optional<T>: https://en.cppreference.com/w/cpp/utility/optional

Hope это помогает.

0 голосов
/ 09 апреля 2020

Есть статья Б. Страуструпа, "Обтекание вызовов функций-членов C ++" , в которой описывается общий метод. В абстракции говорится:

В этой статье представлено простое, общее и эффективное решение старой проблемы «обтекания» вызовов объекта парами префиксного и суффиксного кода. Решение также не навязчиво, применимо к существующим классам, позволяет использовать несколько пар префиксов / суффиксов, [...]

IIR C, оболочка Страуструпа чем-то напоминает умный указатель и он запускает код в своем operator->() и запускает другой код после запуска функции обернутого объекта (подробности см. в статье). Этот подход имеет несколько ограничений, см. Конец статьи.

В зависимости от ваших конкретных потребностей, вы можете использовать этот подход. В вашем примере, оболочка просто возвращает 0 в каждой функции, если она имеет значение nullptr , поэтому она может работать. Для более сложного поведения это может быть не так.

Обновление : См. Ответ @ Jarod42. Оболочка Страуструпа - более сложная версия этого. Для вашего случая / примера, при переносе nullptr operator->() может возвращаться указатель на какой-то другой заданный фиксированный экземпляр.

0 голосов
/ 04 апреля 2020

Не идеально в вашем случае, но operator-> может помочь:

template<class T>
struct Wrapped {
    Wrapped(T* t): _t(t){ }

    const T* operator -> () const { if (_t == nullptr) throw std::logic_error("null pointer"); return _t; }
    T* operator -> () { if (_t == nullptr) throw std::logic_error("null pointer"); return _t; }

    T* _t;
};

переадресация любому члену. это позволяет иметь код до и после (с дополнительным RAII) вызова.

...