策略类设计,但不将整个用户类作为模板 [英] Policy class design but without making the whole user class a template

查看:62
本文介绍了策略类设计,但不将整个用户类作为模板的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下代码,其中 Writer_I 充当接口.可以从中派生出满足正确形式的元素类型约定的其他类.在这里,选择printf和stream作为策略,选择 Calculator 作为用户.

Consider the following code where the Writer_I acts as an interface. Other classes which fulfil the contract of writing element types in correct form can derive from it. Here, printf and streams are chosen as policies, and Calculator as user.

该接口以某种方式存储在 Calculator 中,并且 write_i 隐藏了模板的所有丑陋细节,从而使类成员函数保持简洁.大多数事情在编译时仍然是已知的,并且可以内联.

That interface is somehow stored in Calculator and write_i hides all the ugly details of templates so that class member functions remain clean. Most things remain known at compile time, and inline-able.

我知道这是基于虚拟+派生多态性的经典案例,其中非模板接口可以存储在 Calculator 中,并调用 write 成员函数.但是在编译时知道类型,并且仍然将解析时间推迟到运行时似乎很糟糕.它暗示某些运行时值会影响所选的编写方法,而事实并非如此.

I know this is a classic case of virtual + derivation based polymorphism where a non-templated interface can be stored inside Calculator and write member function is called. But having known the type at compile time, and still deferring resolution to run time seems bad. It hints that some run time value will affect the chosen method of writing while that is not the case.

一种方法可能是使 Calculator 成为模板,并将其实现保存在cpp文件中,并将cpp文件包含在测试中.真讨厌 Calculator 的每个方法顶部都会有一个无用的 template<> .而且它只实例化一次.(如果可以进行测试,可以进行两次测试,但是如果使 Calculator 成为模板的唯一原因是测试,则可以说测试太具有侵入性了.)

One way could be to make Calculator a template and keep its implementation in a cpp file and include the cpp file in tests. That's just nasty. Every method of Calculator will have a useless template <> on the top. And it's getting instantiated only once. (Twice if you could tests, but then if the only reason to make Calculator a template was tests, I'd say that tests are being too intrusive.)

我看到了谈话 https://www.youtube.com/watch?v=mU_n_ohIHQk(元多态性-Jonathan Boccara-会议C ++ 2020开幕主题演讲) https://meetingcpp.com/mcpp/slides/2020/meta_polymorphism_pdf3243.pdf

I saw the talk https://www.youtube.com/watch?v=mU_n_ohIHQk (Meta Polymorphism - Jonathan Boccara - Meeting C++ 2020 Opening Keynote) https://meetingcpp.com/mcpp/slides/2020/meta_polymorphism_pdf3243.pdf

展示了一种使用 std :: any (将存储Writer_I实例引用)+ lambda(包含实际的Impl类型)+函数指针(稍后可以调用)的技术.幻灯片79-83.我尝试过,但很快就被卡住了:如何拥有一个泛型lambda的函数指针?

which showed a technique with std::any (which will store the Writer_I instance reference) + lambda (which contains the actual Impl type) + function pointer (which can be called later). Slides 79-83. I tried but got stuck real quick: How to have a function pointer to a generic lambda?

出于好奇,在所有这些徒劳的尝试之后,我的解决方案是使用迭代器模式,并使 Calculator 免于编写"的责任. Calculator 应该只是计算数据,而不是写入数据.那解决了问题!调用方通过运行 iterator ++ 来获取数据,并按其喜欢的方式进行写入.甚至可能没有写出来,而只是直接测试数字. Calculator 仍然是非模板,因此在cpp文件中.

My solution, after all these futile attempts out of curiosity, would be to use iterator pattern and free the Calculator from the responsibility of "writing". Calculator should just be calculating data, not writing it. That solves the problem! Caller gets the data by running iterator++ and writes it any way it likes. Or may not even write it, but just test the numbers directly. Calculator remains a non template, thus in cpp files.

但是,如果有任何方法可以实现当前设计的目标,我将很高兴看到它.我知道有一些相互矛盾的约束,例如使用类型擦除可能在内部使用虚拟方法,但是出于对堆栈溢出的好奇心,对(;?

But if there's any way to achieve what I intend with the current design, I'd be happy to see it. I know there are some contradictory constraints, like using type erasure which may internally use virtual but curiosity is allowed on Stack Overflow, right (; ?

https://godbolt.org/z/W74833

要澄清一下,这里的用户类是 Calculator ,它不应是模板.所有作者都可以保留在标题中,而不必隐藏.对于CRTP,实际上在 main 中需要知道每个编写器实现的功能.

to clarify, here the user class is Calculator which should not be a template. All writers can remain in headers and need not be hidden. For CRTP, it is actually needed in main to know what each writer implementation does.

#include <any>
#include <iostream>
#include <type_traits>
#include <utility> 

enum class Elem {
  HEADER,
  FOOTER,
};

template <typename Impl> class Writer_I {
public:
  template <Elem elemtype, typename... T> decltype(auto) write(T &&...args) {
    return static_cast<Impl *>(this)->template write<elemtype>(
        std::forward<T>(args)...);
  }
  virtual ~Writer_I() {}
};

class Streams : public Writer_I<Streams> {
public:
  template <Elem elemtype, std::enable_if_t<elemtype == Elem::HEADER, int> = 0>
  void write(int a) {
    std::cout << a << std::endl;
  }
  template <Elem elemtype, std::enable_if_t<elemtype == Elem::FOOTER, int> = 0>
  void write(float a) {
    std::cout << "\n-------\n" << a << std::endl;
  }
};

class Printf : public Writer_I<Printf>{
public:
  template <Elem elemtype, std::enable_if_t<elemtype == Elem::HEADER, int> = 0>
  void write(int a) {
    std::printf("%d\n", a);
  }
  template <Elem elemtype, std::enable_if_t<elemtype == Elem::FOOTER, int> = 0>
  void write(float a) {
    std::printf("\n--------\n%f\n", a);
  } 
};

/* Restrictions being that member functions header and footer
   remain in cpp files. And callers of Calculator's constructor
   can specify alternative implementations. */
class Calculator {
  std::any writer;

public:
  template <typename Impl>
  Calculator(Writer_I<Impl> &writer) : writer(writer) {}

  template <Elem elemtype, typename... T> void write_i(T &&...args) {
    // MAGIC_CAST ----------------------↓
    auto a = std::any_cast<Writer_I<Printf>>(writer);
    a.write<elemtype>(std::forward<T>(args)...);
  }

  void header() {
    for (int i = 0; i < 10; i++) {
      write_i<Elem::HEADER>(i);
    }
  }

  void footer() {
    write_i<Elem::FOOTER>(-100.0f);
  }
};
int main() {
  Streams streams;
//   Calculator calc_s(streams); // throws bad_cast.
//   calc_s.header();
//   calc_s.footer();


  Printf printf_;
  Calculator calc_p(printf_); 
  calc_p.header();
  calc_p.footer();
  return 0;
}

推荐答案

您的设计约束是 Calculator 不必是模板,而必须使用writer进行初始化.

Your design constraint is that Calculator needs to not be a template, and has to be initialized with a writer.

这意味着它与编写器的接口必须是动态的.它可以通过虚拟接口类,通过存储函数指针或在以后传递指针或类似方式来实现.

That means its interface with the writer has to be dynamic. It can be through a virtual interface class, by storing function pointers, or by being passed pointers later, or similar.

由于您不希望固定writer的多态接口,因此排除了虚拟接口.

As you don't want the polymorphic interface of writer to be fixed, that rules out a virtual interface.

现在,我们可以手动执行此操作.

Now, we can do this manually.

void header() {
  for (int i = 0; i < 10; i++) {
    write_i<Elem::HEADER>(i);
  }
}

void footer() {
  write_i<Elem::FOOTER>(-100.0f);
}

这些是我们需要键入擦除"的电话.我们需要键入擦除以使其签名正确,并记住以后的操作.

those are the calls we need to type erase. We need to type erase down to their signatures, and remember how to do it later.

template<class T>
struct tag_t { using type=T; };
template<class T>
constexpr tag_t<T> tag = {};

template<class Sig, class Any=std::any>
struct any_type_erase;
template<class R, class...Args, class Any>
struct any_type_erase<R(Args...)> {
  std::function<R(Any&, Args&&...args)> operation;

  any_type_erase() = default;
  any_type_erase(any_type_erase const&) = default;
  any_type_erase(any_type_erase &&) = default;
  any_type_erase& operator=(any_type_erase const&) = default;
  any_type_erase& operator=(any_type_erase &&) = default;

  template<class T, class F>
  any_type_erase(tag_t<T>, F&& f) {
    operation = [f=std::forward<F>(f)](Any& object, Args&&...args)->R {
      return f(*std::any_cast<T*>(&object), std::forward<Args>(args)...);
    };
  }

  R operator()(Any& any, Args...args)const {
    return operation(any, std::forward<Args>(args)...);
  }
};

any_type_erase 有点帮助进行装箱操作.对于 const 操作,请传入 std :: any const 作为第二个参数.

any_type_erase is a bit of a helper to do the boxing of the operation. For a const operation, pass in std::any const as the 2nd argument.

添加这些成员:

std::any writer;
any_type_erase<void(int)> print_header;
any_type_erase<void(float)> print_footer;

template<class T>
static auto invoke_writer() {
  return [](auto& writer, auto&&..args) {
    writer.write<T>(decltype(args)(args)...);
  };
}

template<typename Impl>
Calculator(Writer_I<Impl>& writer) :
  writer(writer),
  print_header( tag<Writer_I<Impl>>, invoke_writer<Elem::HEADER>() ),
  print_footer( tag<Writer_I<Impl>>, invoke_writer<Elem::FOOTER>() )
{}

void header() {
  for (int i = 0; i < 10; i++) {
    print_header( writer, i );
  }
}

void footer() {
  print_footer( writer, -100.0f );
}

这篇关于策略类设计,但不将整个用户类作为模板的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆