共计 5780 个字符,预计需要花费 15 分钟才能阅读完成。
前言
回顾上回
回忆上回为了写一个 memento 模式(请看 谈 C++17 里的 Memento 模式),感觉仅仅 memento 太干瘪了,罗唆就写了个类库 undo-cxx,也真是没谁了。
这两日想来想去,愈发感觉这事干得太那啥了。今后还是不用如此的吧?话说这两天头发都不长长了,担心啊。
本文缘起
在 谈 C++17 里的 Factory 模式 中我介绍了 hicc/cmdr-cxx 中的 factory 模板类,看了一下时间表,动念是 0822,竟然这么长时间了(而且都三个月了,写个 GoF 系列居然也没写进去,我不应该这么懒的)。过后提到 factory 的存在的 T data
问题,即在 factory 的 tuple 中持有每个 products 类的一个具体化实例,起因是为了稍后可能从 T data 中抽出类型供 create 应用。
这显然是一个不难受的货色。
然而过后不想纠缠了,问题就这么遗留下来了,直到起初某一天感到了不能忍,才去钻研了怎么毁灭这玩意,事实上它的确是能够被毁灭的。
factory<>
改进版
所以当初改良的版本是:
namespace cmdr::util::factory {
/**
* @brief a factory template class
* @tparam product_base such as `Shape`
* @tparam products such as `Rect`, `Ellipse`, ...
*/
template<typename product_base, typename... products>
class factory final {
public:
CLAZZ_NON_COPYABLE(factory);
using string = id_type;
template<typename T>
struct clz_name_t {string id = id_name<T>();
using type = T;
using base_type = product_base;
static void static_check() {static_assert(std::is_base_of<product_base, T>::value, "all products must inherit from product_base");
}
template<typename... Args>
std::unique_ptr<base_type> gen(Args &&...args) const {return std::make_unique<T>(args...);
}
// T data;
};
using named_products = std::tuple<clz_name_t<products>...>;
template<typename... Args>
static auto create(string const &id, Args &&...args) {std::unique_ptr<product_base> result{};
std::apply([](auto &&...it) {((it.static_check() /*static_check<decltype(it.data)>()*/), ...);
},
named_products{});
std::apply([&](auto &&...it) {((it.id == id ? result = it.gen(args...) : result), ...);
},
named_products{});
return result;
}
template<typename... Args>
static std::shared_ptr<product_base> make_shared(string const &id, Args &&...args) {std::shared_ptr<product_base> ptr = create(id, args...);
return ptr;
}
template<typename... Args>
static std::unique_ptr<product_base> make_unique(string const &id, Args &&...args) {return create(id, args...);
}
template<typename... Args>
static product_base *create_nacked_ptr(string const &id, Args &&...args) {return create(id, args...).release();}
private:
}; // class factory
} // namespace cmdr::util::factory
在这个改进版中,咱们通过在 clz_name_t 中定义一个 generator 函数的形式来结构 T 的最终实例,而不用借助于 decltype(T data) 这样的运算来取得 T 类型,所以可能顺利地打消 T data。
顺便也改写了 static_assert 函数,这个函数仅被用于编译期。
在 create() 中的两次 named_products{} 实例实际上会在 release build 时被优化为单次。
遗憾的是
仍未能解决的是大量 products(例如数千个)时遍历 named_products{} 导致的可能的性能问题。因为没有适合的参数包开展语法,这个问题仍然还是被搁置,今后有了念头再来补充一次咯。
侥幸的是,个别状况下这并不会真是个问题。
改进版的 type_name,以及 id_name
在 factory<>
新版本中应用了新的 id 名算法 id_name,它从类型 T 抽出其类型名表述(如同 word_processor::FontStyleCmd<State>
这样),而后去掉泛型参数局部,留下 word_processor::FontStyleCmd
,这样更适宜于被其余场合所应用。/
改良的 type_name
此前并未专门展现 type_name 的实现,你须要去查看源代码才行。另外,旧的实现存在肯定的兼容性问题,尤其是在 msvc 中始终是勉强工作。
所以,也不能忍,改掉:
namespace cmdr::debug{
template<typename T>
constexpr std::string_view type_name();
template<>
constexpr std::string_view type_name<void>() { return "void";}
namespace detail {
using type_name_prober = void;
template<typename T>
constexpr std::string_view wrapped_type_name() {
#ifdef __clang__
return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
return __FUNCSIG__;
#else
#error "Unsupported compiler"
#endif
}
constexpr std::size_t wrapped_type_name_prefix_length() {return wrapped_type_name<type_name_prober>().find(type_name<type_name_prober>());
}
constexpr std::size_t wrapped_type_name_suffix_length() {return wrapped_type_name<type_name_prober>().length() - wrapped_type_name_prefix_length() - type_name<type_name_prober>().length();
}
template<typename T>
constexpr std::string_view type_name() {constexpr auto wrapped_name = wrapped_type_name<T>();
constexpr auto prefix_length = wrapped_type_name_prefix_length();
constexpr auto suffix_length = wrapped_type_name_suffix_length();
constexpr auto type_name_length = wrapped_name.length() - prefix_length - suffix_length;
return wrapped_name.substr(prefix_length, type_name_length);
}
} // namespace detail
template<typename T>
constexpr std::string_view type_name() {constexpr auto r = detail::type_name<T>();
using namespace std::string_view_literals;
constexpr auto pr1 = "struct"sv;
auto ps1 = r.find(pr1);
auto st1 = (ps1 == 0 ? pr1.length() : 0);
auto name1 = r.substr(st1);
constexpr auto pr2 = "class"sv;
auto ps2 = name1.find(pr2);
auto st2 = (ps2 == 0 ? pr2.length() : 0);
auto name2 = name1.substr(st2);
constexpr auto pr3 = "union"sv;
auto ps3 = name2.find(pr3);
auto st3 = (ps3 == 0 ? pr3.length() : 0);
auto name3 = name2.substr(st3);
return name3;
}
template<typename T>
constexpr auto short_type_name() -> std::string_view {constexpr auto &value = type_name<T>();
constexpr auto end = value.rfind("::");
return std::string_view{value.data() + (end != std::string_view::npos ? end + 2 : 0)};
}
}
它可能良好地兼容三种编译器,当然必须是 C++17 模式。
测试代码
class test;
int main() {
using std::cout;
using std::endl;
using namespace dp::debug;
cout << "test :" << type_name<test>() << endl;
cout << "const int*& :" << type_name<const int *&>() << endl;
cout << "unsigned int :" << type_name<unsigned int>() << endl;
const int ic = 42;
const int *pic = ⁣
const int *&rpic = pic;
cout << "const int :" << type_name<decltype(ic)>() << endl;
cout << "const int* :" << type_name<decltype(pic)>() << endl;
cout << "const int*& :" << type_name<decltype(rpic)>() << endl;
cout << "void :" << type_name<void>() << endl;
cout << "std::string :" << type_name<std::string>() << endl;
cout << "std::vector<std::string> :" << type_name<std::vector<std::string>>() << endl;}
的运行反馈是:
test : test
const int*& : const int *&
unsigned int : unsigned int
const int : const int
const int* : const int *
const int*& : const int *&
void : void
std::string : std::__1::basic_string<char>
std::vector<std::string> : std::__1::vector<std::__1::basic_string<char>, std::__1::allocator<std::__1::basic_string<char> > >
Id_name
在 type_name 的根底上,id_name 可能将局部修饰词去掉,另外对于 std::__1::basic_string<char>
它会去掉其泛型参数局部:
namespace cmdr::util {#if defined(_MSC_VER)
using id_type = std::string_view; // or std::string_view
#else
using id_type = std::string_view;
#endif
template<typename T>
constexpr auto id_name() -> id_type {constexpr id_type v = debug::type_name<T>();
constexpr auto begin = v.find("()::");
constexpr auto end = v.find('<');
constexpr auto begin1 = begin != v.npos ? begin + 4 : 0;
return v.substr(begin1, (end != v.npos ? end : v.length()) - begin1);
}
} // namespace cmdr::util
修饰词是指 void func()::
这样的前缀,如果你在函数体中申明一个 struct,就可能失去这样的前缀。
后记
称得上技巧的就只有一个了,本文目标是连续和让系列化文章残缺,省得过期的实现受到诟病。