【问题标题】:Provide PV function content when constructing object in C++C++构造对象时提供PV函数内容
【发布时间】:2018-10-21 19:55:31
【问题描述】:

在 Java 中,您可以在创建对象的同时在对象中提供(或重载)抽象函数,因此:

ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // Whatever in here
    }
};

我真的很喜欢这种方式,并且想知道 C++ 中是否有类似的构造。

基本上,我想要一个基类,其中声明了几个 PV 函数(以及其他内容),并且用户创建该类的实例,同时提供 PV 函数的主体。

我知道我可以创建子类,但这对于我需要的东西来说似乎有点笨拙,每个子类都是唯一的,并且每个子类只能用于创建一个实例。

我考虑过为构造函数提供 lamdas 并使用它们而不是实际的成员函数,但这对于新手用户来说确实看起来很混乱而且很难理解 - 更不用说它太死板了(我' d 还希望能够选择性地覆盖一些非纯虚函数。

那么子类是唯一的方法吗,还是在一些我不知道的较新的 C++ 标准中存在一些鲜为人知的构造可以做我想要的?


稍微扩展一下——我们的想法是有一个像这样的类:

class Thread {
    // other stuff
    public:
        virtual void setup() = 0;
        virtual void loop() = 0;
        // other functions, some virtual but not pure
};

Thread threadOne {
    void setup() {
        // Init code for this thread
    }
    void loop() {
        // Run code for this thread
    }
};

Thread threadTwo {
    void setup() {
        // Init code for this thread
    }
    void loop() {
        // Run code for this thread
    }
};

显然不是那种语法,但它让你知道我想如何使用这个类。

它旨在在具有精简 C++ 实现的嵌入式系统上运行(它是 g++,但没有完整的 STL)。最终用户并不是最聪明的一群,因此必须尽可能简单易懂。


匿名子类最接近我想要的(尽管仍然不完美)。我可以使用 CPP 宏来帮助抽象一些有帮助的类实现语法糖。


这是我想出的一个可编译结构。考虑到上述限制,这种方法有什么“错误”吗?

#define THREAD(NAME, CONTENT) class : public Thread {\
    public:\
CONTENT\
} NAME;


class Thread {
    private:
        uint32_t stack[256]; // 1kB stack
        volatile bool _running;

    public:
        virtual void setup() = 0;
        virtual void loop() = 0;

        void start();
        void stop();
        uint8_t state();

        static void spawn(Thread *thr);
        void threadRunner();
};

void Thread::spawn(Thread *thread) {
    thread->threadRunner();
}

void Thread::start() {
    Thread::spawn(this);
}

void Thread::threadRunner() {
    _running = true;
    setup();
    while (_running) {
        loop();
    }
}

void Thread::stop() {
    _running = false;
}

uint8_t Thread::state() {
    return 0;
}

THREAD(myThread,
    void setup() override {

    }

    void loop() override {

    }
)

void setup() {
    myThread.start();
}

void loop() {
}

显然它实际上还没有任何事情 - 整个线程后端是一个单独的问题,并将从我几年前编写的一些现有代码移植过来。我主要对简化最终用户的界面感兴趣。

【问题讨论】:

  • 你可以使用匿名类,但我会坚持使用 lambdas。
  • @Vorac 匿名课程看起来是我目前最喜欢的选择。
  • 我建议不要这样做。 C++ 中的当前“时尚”如下。不惜一切代价避免使用宏。如果可以使用组合,请避免继承。使用函数式语言方法,例如通过 stl 算法传递函数或folding 容器。最后,由于切片、菱形、效率、无法重载虚拟方法等原因,虚拟表很麻烦。
  • 你能详细说明一下吗?
  • 从您更新的问题中,我可以看到您想要实现灵活的线程。我在 C++ 中看到了许多 可怕 的自定义线程实现,并且确信我可以写出一个好的。请盗版或购买“Scott Meyers:有效的现代 C++”。阅读第 7 章(共 40 页)。

标签: c++ class pure-virtual


【解决方案1】:

有多种可能性,但我会坚持使用简单而通用的方法:回调和 lambda,而不是虚函数和继承。

class ActionListener
{
    std::function<void(int)> _action_performed;
public:
    template<class CB>
    ActionListener(CB cb) : _action_performed(cb) {}

    void click() { _action_performed(0); }
};

int main()
{
    ActionListener al([](int n) { std::cout << "Action Performed #" << n << "\n"; });
    al.click(); // prints "Action Performed #0"
}

live demo


我还希望能够选择性地覆盖一些非纯虚函数

从语义上讲,这意味着提供默认行为。这是可能的:

ActionListener(CB cb) : _action_performed(cb) {} // construct an AL with the given callback

ActionListener() : _action_performed(default_action_performed) {} // construct an AL with a default callback
void default_action_performed(int n) { /*...*/ }

【讨论】:

  • 像这样的默认值需要特定的顺序 - 您不能按名称提供它们并跳过一些以仅提供列表中的某些功能。
  • @Majenko 是的,没错。与 Java 不同,没有编译器支持或语法糖来做你想做的事。您必须使用该语言的其他工具。如果您希望能够以未指定的顺序传递多个回调,则需要传递名称映射函数。
  • @Majenko 但是你最好问问自己这是否是最好的方法。为什么要首先避免使用 lambda?
  • 它适用于嵌入式系统,我希望最终用户能够将他们现有的单线程代码直接复制并粘贴到任何结构中,而无需进行任何重大修改。在这种情况下,最终用户通常有点厚,甚至不知道 lambdas 是什么。目前,子类看起来与 Java 结构最匹配。此外,不存在 STL 之类的东西。
  • @Majenko 在你的问题中这么说不会浪费打字......无论如何,C++ hard。你不能为愚蠢的开发者设计工具,反正他们会自取其辱。
【解决方案2】:

好吧,正如您已经提到的,一种方法是子类。

另一种方法是提供一些 std::functions(或 lambdas),或者在构造函数中或者有一些设置函数。

将函数存储为成员,并在调用“虚拟”成员函数后调用它:如果您希望它是可选的:

class MyBase
{
public:
   MyBase();
   void SetFunc(const std::function<int()>& myFun)
   {
      m_myFun = myFun;
   }

   int MyVirtFunc()
   {
      if(m_myFun)
      {
         return m_myFun();
      }
      else
      {
         return 42;
      }
   }   
private:
   std::function<int()> m_myFun;
}

如果你想要强制给出的函数,把它们放在构造函数中:

class MyBase
{
public:
   MyBase(const std::function<int()>& myFun)
       : m_myFun(myFun) {}

   int MyVirtFun()   {  return m_myFun(); }
private:
   const std::function<int()> m_myFun;
}

【讨论】:

  • 如果可能,我想避免使用 lambda 和 setter。
  • @Majenko 如果您不想要设置器,请使用第二个示例(在构造函数中提供函数)。代码中没有 lambda,只有 std::functions。你可以把任何可转换的东西放进去。
  • 它适用于嵌入式系统,我希望最终用户能够将他们现有的单线程代码直接复制并粘贴到任何结构中,而无需进行任何重大修改。在这种情况下,最终用户通常有点厚,甚至不知道 lambdas 是什么。目前,子类看起来最接近 Java 构造。此外,不存在 STL 之类的东西。
  • @Majenko 在这种情况下选择子类。确保他们对被覆盖的函数使用“override”关键字(这样可以确保基类中存在具有完全相同签名的函数)。
猜你喜欢
  • 1970-01-01
  • 2011-02-20
  • 1970-01-01
  • 2011-05-15
  • 2018-04-29
  • 1970-01-01
  • 2019-07-28
  • 1970-01-01
  • 2013-08-29
相关资源
最近更新 更多