Есть ли простой способ проверить небезопасное выражение в C ++? - PullRequest
2 голосов
/ 28 октября 2019

Я пытаюсь найти [лучший] способ запустить / проверить потенциально небезопасное выражение или выполнить несколько пустых проверок более элегантным способом.

Вот пример кодов, которые я хотел бы улучшить:

if (myObjectPointer &&
    myObjectPointer->getSubObject() &&
    myObjectPointer->getSubObject()->getSubSubObject() &&
    myObjectPointer->getSubObject()->getSubSubObject()->getTarget()) {

  // Use safely target
  ... *(myObjectPointer->getSubObject()->getSubSubObject()->getTarget()) ...
}

Я попытался найти более элегантный способ добиться этого (вместо приведенных выше подробных проверок на ноль). Вот мои первые мысли:

template<typename T>
bool isSafe(T && function) {
   try {
       function(); 
       // Just running the func above, but we could e.g. think about returning the actual value instead of true/fase - not that important. 
       return true;
    }
    catch (...) {
       return false;
    }
}

...
// And use the above as follow :
if(isSafe([&](){ myObjectPointer->getSubObject()->getSubSubObject()->getTarget(); })) {
    // Use safely target
}
...

Проблема с вышесказанным в том, что мы не можем поймать сигналы (ошибка сегментации, ...). И я, очевидно, не хочу обрабатывать все сигналы в программе, но только в этой очень специфической функции check / eval.

Я неправильно решаю проблему? Любые другие рекомендации? или многословно, если неизбежно?

Большое спасибо заранее.

Ответы [ 3 ]

2 голосов
/ 29 октября 2019

Я думал об этом, и, как сказал Jarod42, должен быть какой-то вариационный шаблон. Я не лучший в этом, но придумал следующее:

#include <memory>
#include <functional>
#include <iostream>

template <typename T, typename MemFn, typename... Params> 
void safeExecute(T* ptr, MemFn memFn, Params&&... params) {
    if (ptr != nullptr)
        safeExecute(std::invoke(memFn, ptr), std::forward<Params>(params)...);
}

template <typename T, typename MemFn>
void safeExecute(T* ptr, MemFn memFn) {
    if (ptr != nullptr) std::invoke(memFn, ptr);
}


struct Target {
    void Bar() { std::cout << "tada!\n"; };
};


template<typename T>
class Object {
private:
    std::unique_ptr<T> ptr;
public:
    Object() : ptr(std::make_unique<T>()) {}

    T* Get() { return ptr.get(); }
};

using SubSubObject = Object<Target>;
using SubObject = Object<SubSubObject>;
using MyObject = Object<SubObject>;

int main() {
    auto myObjectPtr = std::make_unique<MyObject>();

    safeExecute(myObjectPtr.get(),
                &MyObject::Get,
                &SubObject::Get,
                &SubSubObject::Get,
                &Target::Bar);
}

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

#include <memory>
#include <functional>
#include <iostream>
#include <optional>

template <typename T, typename MemFn, typename... Params>
auto safeGetObject(T* ptr, MemFn memFn, Params&&... params)
    -> decltype(safeGetObject(std::invoke(memFn, std::declval<T>()), std::forward<Params>(params)...))
{
    if (ptr != nullptr) return safeGetObject(std::invoke(memFn, ptr), std::forward<Params>(params)...);
    return {};
}

template <typename T, typename MemFn>
auto safeGetObject(T* ptr, MemFn memFn) -> std::optional<decltype(std::invoke(memFn, std::declval<T>()))> {
    if (ptr != nullptr) return std::invoke(memFn, ptr);
    return {};
}

struct Target {
    int Bar(int a, int b) const noexcept {
        return a+b;
    };
};

template<typename T>
class Object {
private:
    std::unique_ptr<T> ptr;
public:
    Object() noexcept : ptr(std::make_unique<T>()) {}

    T* Get() const noexcept { return ptr.get(); }
};

using SubSubObject = Object<Target>;
using SubObject = Object<SubSubObject>;
using MyObject = Object<SubObject>;

int main() {
    auto myObjectPtr = std::make_unique<MyObject>();

    auto optionalTarget = safeGetObject(
        myObjectPtr.get(),
        &MyObject::Get,
        &SubObject::Get,
        &SubSubObject::Get);

    auto result = optionalTarget ? optionalTarget.value()->Bar(3, 4) : -1;
    std::cout << " result " << result << '\n';
}
1 голос
/ 29 октября 2019

Оставляя в стороне возможные проблемы с дизайном, вы можете использовать расширенную версию std::optional. Поскольку не все интерфейсы находятся под вашим контролем, вы должны были бы обернуть необходимые функции в свободную функцию. Предположим, вы можете изменить класс MyClass из myObjectPointer, но не классы подобъектов.

class MyClass  {
public:
    optional<std::reference_wrapper<SubObjectClass>> getSubObject();
};

optional<std::reference_wrapper<SubSubObjectClass>> getSubSubObject(SubObjectClass& s) {
    SubSubObjectClass* ptr = s.getSubSubObject();

    if (ptr) {
        return std::ref(s.getSubSubObject());
    } else {
        return {};
    }
}

optional<std::reference_wrapper<Target>> getTarget(SubSubObjectCLass& s) {
    ...
}

Теперь вы можете написать что-то вроде

optional<MyClass*>  myObjectPointer = ...;
myObjectPointer.and_then(MyClass::getSubObject)
               .and_then(getSubSubObject)
               .and_then(getTarget)
               .map( doSomethingWithTarget ):
0 голосов
/ 29 октября 2019

ОК, я мог бы удалить свой предыдущий ответ, потому что я переосмыслил это, теперь рассматриваю возможность использования std :: option и chaining. Ваш оригинальный

myObjectPointer->getSubObject()->getSubSubObject()->getTarget()

на самом деле не воспроизводим, поскольку operator->() не может быть статичным. Но мы можем использовать другой оператор, например operator>>(). Таким образом:

#include <memory>
#include <iostream>
#include <optional>
#include <functional>

struct Target {
    int Bar(int a, int b) const noexcept { return a+b; };
};

template<typename T>
class Object {
private:
    T* const ptr;
public:
    Object(T* ptr) noexcept : ptr(ptr) {}
    T* Get() const noexcept { return ptr; }
};

using SubSubObject = Object<Target>;
using SubObject = Object<SubSubObject>;
using MyObject = Object<SubObject>;

template <typename T>
auto makeOptional(T* ptr) -> std::optional< std::reference_wrapper<T>> {
    if (ptr) return std::ref(*ptr);
    return {};
}

template <typename T, typename MemFn>
auto operator>> (std::optional<std::reference_wrapper<T>> optObj, MemFn memFn)
-> std::optional< std::reference_wrapper<std::remove_pointer_t<decltype(std::invoke(memFn, std::declval<T>()))>>> {
    if (optObj) return makeOptional(std::invoke(memFn, *optObj));
    return {};
}


int main() {
    {
        //complete
        auto TargetPtr = std::make_unique<Target>();
        auto subSubObjectPtr = std::make_unique<SubSubObject>(TargetPtr.get());
        auto subObjectPtr = std::make_unique<SubObject>(subSubObjectPtr.get());
        auto myObjectPtr = std::make_unique<MyObject>(subObjectPtr.get());

        auto optionalMyObject = makeOptional(myObjectPtr.get());

        auto optionalTarget = optionalMyObject >> &MyObject::Get >> &SubObject::Get >> &SubSubObject::Get;

        auto result = (optionalTarget) ? optionalTarget->get().Bar(3, 4) : -1;

        std::cout << "result is " << result << '\n';
    }
    {
        // incomplete
        auto subObjectPtr = std::make_unique<SubObject>(nullptr);
        auto myObjectPtr = std::make_unique<MyObject>(subObjectPtr.get());

        auto optionalMyObject = makeOptional(myObjectPtr.get());

        auto optionalTarget = optionalMyObject >> &MyObject::Get >> &SubObject::Get >> &SubSubObject::Get;

        auto result = (optionalTarget) ? optionalTarget->get().Bar(3, 4) : -1;

        std::cout << "result is " << result << '\n';
    }
}

будет работать ... Дайте мне знать, если это то, что вы ищете.


edit: я также пытался поместить его в оболочкукласс

#include <memory>
#include <iostream>
#include <functional>
#include <optional>

struct Target {
    constexpr int Bar(int a, int b) const noexcept { return a + b; };
};

template<typename T>
class Object {
private:
    T* const ptr;
public:
    constexpr Object(T* const ptr) noexcept : ptr(ptr) {}
    constexpr T* Get() const noexcept { return ptr; }
};

using SubSubObject = Object<Target>;
using SubObject = Object<SubSubObject>;
using MyObject = Object<SubObject>;

template<typename T>
class ObjectWrapper {
private:
    std::optional<std::reference_wrapper<T>> optRefObj{};
public:
    constexpr ObjectWrapper(T* ptr) noexcept
        : optRefObj(ptr ? std::make_optional(std::ref(*ptr)) : std::nullopt)
    {}

    template<typename MemFn>
    constexpr auto operator>>(MemFn memFn) const noexcept {
        return ObjectWrapper<std::remove_pointer_t<decltype(std::invoke(memFn, std::declval<T>()))>>
            (optRefObj ? std::invoke(memFn, *optRefObj) : nullptr);
    }

    constexpr operator bool() const noexcept { return optRefObj.has_value(); }

    constexpr T* Get() noexcept { return optRefObj ? &optRefObj->get() : nullptr; }
};

int main() {
    {
        //complete
        auto const TargetPtr = std::make_unique<Target>();
        auto const subSubObjectPtr = std::make_unique<SubSubObject>(TargetPtr.get());
        auto const subObjectPtr = std::make_unique<SubObject>(subSubObjectPtr.get());
        auto const myObjectPtr = std::make_unique<MyObject>(subObjectPtr.get());

        auto const myObjWrp = ObjectWrapper(myObjectPtr.get());

        auto optionalTarget = myObjWrp >> &MyObject::Get >> &SubObject::Get >> &SubSubObject::Get;

        auto const result = optionalTarget ? optionalTarget.Get()->Bar(3, 4) : -1;

        std::cout << "result is " << result << '\n';
    }
    {
        // incomplete
        auto const subObjectPtr = std::make_unique<SubObject>(nullptr);
        auto const myObjectPtr = std::make_unique<MyObject>(subObjectPtr.get());

        auto const myObjWrp = ObjectWrapper(myObjectPtr.get());

        auto optionalTarget = myObjWrp >> &MyObject::Get >> &SubObject::Get >> &SubSubObject::Get;

        auto const result = optionalTarget ? optionalTarget.Get()->Bar(3, 4) : -1;

        std::cout << "result is " << result << '\n';
    }
}
...