谈 C++17 里的 Factory 模式之二

乐肇
• 阅读 1953

前言

回顾上回

回想上回为了写一个 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 = &ic;
  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,就可能得到这样的前缀。

后记

称得上技巧的就只有一个了,本文目的是延续和让系列化文章完整,免得过时的实现遭到诟病。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
4年前
List的Select 和Select().tolist()
List<PersondelpnewList<Person{newPerson{Id1,Name"小明1",Age11,Sign0},newPerson{Id2,Name"小明2",Age12,
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
4年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这