我有一个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()
并使宏在任何地方都可用?
编辑: 请注意,由于项目限制,很遗憾不能使用例外。
答案 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));
}
}
答案 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;,...(假设默认构造的静态没有令人讨厌的副作用,而且它以一种“理智的方式”使用...... )