头图

Preface

Review last time

Recalling the last time I wrote a memento mode (see about Memento mode C++17), I felt that memento was too dry, so I wrote a class library undo-cxx , which is really nothing.

After thinking about it these two days, I feel more and more that I have done too much of this matter. It doesn’t have to be this way in the future, right? By the way, my hair has not grown any longer in these two days, so worried.

Origin of this article

In talk about the Factory mode , I introduced the factory template class in hicc/cmdr-cxx. I looked at the timetable and found that it has been so long (and it’s been three months). , I didn’t write a GoF series, I shouldn’t be so lazy). T data of the existence of the factory was mentioned, that is, a concrete instance of each products class is held in the tuple of the factory. The reason is to be able to extract the types from the T data for use in create later.

This is obviously an uncomfortable thing.

But I didn't want to be entangled at the time, the problem was left behind. It was not until one day later that I felt intolerable, and then I went to study how to eliminate this thing. In fact, it can indeed be eliminated.

factory<> improved version

So now the improved version is:

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

In this improved version, we construct the final instance of T by defining a generator function in clz_name_t, without resorting to operations such as decltype(T data) to obtain the T type, so T data can be eliminated smoothly.

By the way, the static_assert function is also rewritten. This function is only used during compilation.

The two named_products{} instances in create() will actually be optimized as a single time in the release build.

Regrettably

What remains unsolved is the possible performance problem caused by traversing named_products{} when a large number of products (for example, thousands). Because there is no suitable parameter package to expand the grammar, this problem is still shelved, and I will add it again if I have an idea in the future.

Fortunately, this is not really a problem under normal circumstances.

Improved version of type_name, and id_name

In factory<> new version in a new algorithm id_name the id, it extracts its type name representation (as in the type T word_processor::FontStyleCmd<State> so), and then remove the portion of the generic parameters, leaving word_processor::FontStyleCmd , which is more adapted to be used in other locations. /

Improved type_name

The implementation of type_name was not specifically shown before, you need to check the source code. In addition, the old implementation has certain compatibility issues, especially in msvc, which has been reluctant to work.

Therefore, I can't bear it, change:

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)};
}
}

It can be well compatible with three compilers, of course it must be C++17 mode.

Test code

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;
}

The running feedback is:

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

On the basis of type_name, id_name can remove some modifiers, and for std::__1::basic_string<char> it will remove its generic parameter part:

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():: refer to prefixes such as 0617527a39721f. If you declare a struct in the function body, you may get such prefixes.

postscript

There is only one technique that can be called a skill. The purpose of this article is to continue and make the series of articles complete, so as not to be criticized for outdated implementations.

:end:


hedzr
95 声望19 粉丝