从函数

时间:2017-10-13 13:28:00

标签: c++ macros initialization type-inference

我有一个class,非常不幸的是,它依赖于两步初始化。这意味着在构造之后,除非调用初始化方法,否则该对象仍未准备好使用:

class A
{
public:
    A();
    bool init();

private:
    bool m_is_initialized;
};

该类的每个其他方法都应遵循此策略:如果在尚未初始化类时调用该方法,则该方法应停止执行并在类特定通道上记录错误。

问题是某些方法有返回类型。在这种情况下,策略是返回返回类型的默认构造值。

我们的想法是拥有一个可以在每个方法实现开始时调用的简单宏,例如:

#define CHECK_INITIALIZED                                                  \
if ( !m_is_initialized )                                                   \
{                                                                          \
    LOG_E( m_channel, "%s object not initialized.", __PRETTY_FUNCTION__ ); \
    assert( false );                                                       \
    return;                                                                \
} 

顺便说一句,return语句仅对void函数有效,并不适用于所有情况。有没有办法推断扩展宏的函数的返回类型T,以便我可以返回T()并使宏在任何地方都可用?

编辑: 请注意,由于项目限制,很遗憾不能使用例外。

4 个答案:

答案 0 :(得分:3)

为什么要使用宏?模板可以很好地处理这个问题。

struct has_init_check
{
protected:
    template<class F>
    auto if_ready(F&& f)
    {
        if (m_is_initialized)
        {
            f();
        }
        else
        {
            // log the error here
        }
    } 

    void notify_initialized()
    {
        m_is_initialized = true;
    }

private:
    bool m_is_initialized = false;
};

class A 
: has_init_check
{
public:
    A();
    bool init() { notify_initialized(); }

    int foo();
    void bar();

};

int A::foo()
{
    int result = 0; // default value
    if_ready([&]
    {
        // logic here
        result = 10;
    });

    return result;    
}

void A::bar()
{
    if_ready([]
    {
        // logic here
    });
}

答案 1 :(得分:0)

您可以使用return {};表示您需要默认的初始化返回类型。但是,如果类型不是默认构造的,您尝试返回引用或返回类型为void,则会失败。

另一种选择是将boost::optional / std:optional作为所有功能的返回类型与return {};结合使用。这使您可以在不执行任何操作时返回默认可选项(因此可选项为空)。

另一个选择是将返回值传递给宏,并将其用于返回,如

#define CHECK_INITIALIZED(default_return_value)                            \
if ( !m_is_initialized )                                                   \
{                                                                          \
    LOG_E( m_channel, "%s object not initialized.", __PRETTY_FUNCTION__ ); \
    return default_return_value;                                                                \
} 

答案 2 :(得分:0)

另一个答案,另一种方法。

不允许例外,但我们仍然可以通过使用变量(错误,对象)来捕获构建时的初始化失败。

我们知道我们对象的所有成员都是nothrow_constructible(这是一个约束)。因此,它们也必须是不可移动的。

因此,我们可以使用variant和optional的组合来管理对象构造,并在失败时记录/暂停。

现在实现方法时没有运行时开销。

#include <variant>
#include <optional>
#include <string>
#include <cstdlib>
#include <iostream>
#include <type_traits>


struct construction_error
{
    construction_error(std::string s)
    : message_(s)
    {}

    const std::string message() const {
        return message_;
    }

    std::string message_;
};

template<class T> using construction_result = std::variant<construction_error, T>;

template<class T> struct tag {};

template<class T, class...Args>
auto construct(tag<T>, Args&&...args) -> construction_result<T>
{
    auto x = T(std::forward<Args>(args)...);
    if (auto result = x.init())
    {
        return std::move(result).value();
    }
    else
    {
        return std::move(x);
    }
}

class A 
{
public:
    A() noexcept { std::cout << "speculative construction" << std::endl; }
    std::optional<construction_error> init() noexcept { 
        if (rand() < RAND_MAX / 2)
        {
            return construction_error("failed to construct an A");
        }
        else
        {
            // complete the construction
            return {};
        }
    }

    int foo();
    void bar();

};

int A::foo()
{
    std::cout << __func__ << std::endl;
    // logic here
    return 10;
}

void A::bar()
{
    std::cout << __func__ << std::endl;
    // logic here
}

void do_thing(A a, A b, A c)
{
    a.foo();
    b.foo();
    c.foo();
    a.bar();
    b.bar();
    c.bar();
}

template<class T>
void maybe_report_failure(const T&)
{
}

void maybe_report_failure(construction_error const& cf)
{
    std::cout << "construction failure: " << cf.message() << std::endl;
}

int main()
{

    for (int i = 0 ; i < 100 ; ++i)
    {
        auto maybe_a_1 = construct(tag<A>());
        auto maybe_a_2 = construct(tag<A>());
        auto maybe_a_3 = construct(tag<A>());

        auto action = [](auto&&...as)
        {
            constexpr bool good = (std::is_same_v<std::decay_t<decltype(as)>, A> && ...);
            if constexpr (good)
            {
                do_thing(std::move(as)...);
            }
            else
            {
                (maybe_report_failure(as), ...);
            }
        };
        std::visit(action, 
            std::move(maybe_a_1), 
            std::move(maybe_a_2), 
            std::move(maybe_a_3));
    }
}

http://coliru.stacked-crooked.com/a/397427a89afa728a

答案 3 :(得分:0)

这应该适用于void,默认可构造+可移动&和引用类型(未测试:) :):

#define CHECK_INITIALIZED_WITH_RETURN(R)                                                 \
if ( !m_is_initialized )                                                   \
{                                                                          \
    LOG_E( m_channel, "%s object not initialized.", __PRETTY_FUNCTION__ ); \
    assert( false );                                                       \
    static std::conditional_t<std::is_same_v<R,void>,int,std::decay_t<R>> some_default{}; \
    return R(some_default);  \
}

其中R可以是void,T,T&amp;,...(假设默认构造的静态没有令人讨厌的副作用,而且它以一种“理智的方式”使用...... )