C++中类成员函数相互调用有啥好处?

Posted

技术标签:

【中文标题】C++中类成员函数相互调用有啥好处?【英文标题】:What is the advantage of class member functions to call each other in C++?C++中类成员函数相互调用有什么好处? 【发布时间】:2017-05-10 06:23:06 【问题描述】:

我是 C++ 新手。我发现下面的编程风格对我来说很有趣。我在这里写了一个简化版。

#include <iostream>
using namespace std;
class MyClass 
    public :
        MyClass(int id_) : id(id_) 
            cout<<"I am a constructor"<<endl;
        
        bool error = false;
        void run() 
            //do something ...
            if (!error) 
                read();
            
        
        void read() 
            //do something ...
            if (!error) 
                write();
            
        
        void write() 
            //do something ...
            if (!error) 
                read();
            
        
    private :
        int id;
;
int main() 
    MyClass mc(1);  
    mc.run();
    return 0;

这里的例子是可编译的,但我没有运行它,因为我必须进入一个无限循环。但是,我希望以此作为参考。 read() 和 write() 互相调用。我第一次遇到这种编程风格是在boost.asio。当服务器在 do_read() 中收到一条消息时,它调用 do_write() 来回显客户端,然后在 do_write() 结束时再次调用 do_read()。

关于这种类型的编码,我有两个问题。

    这会导致堆栈溢出吗?因为函数一直在调用自己并且函数结束只会发生错误。

    它有什么好处?为什么我不能用一个函数让它们有序循环,遇到错误就中断循环。

    bool replied = true;
    
    while (!error) 
    
        if (replied) read();
    
        else 
    
            write();
    
            replied = !replied;
    
        
    
    
    

【问题讨论】:

如果你在做正确的尾调用,编译器应该优化它们以防止调用堆栈溢出。 如何定义“正确”? 表示控制权不必返回给调用者,即return f(x);是尾调用,int y = f(x); return y;不是。 【参考方案1】:

您的简化版本忽略了最重要的方面:write()read() 调用是异步的

因此,函数实际上不会导致递归,请参阅最近的答案:Do "C++ boost::asio Recursive timer callback" accumulate callstack?

async_read(...)async_write(...) 的“不寻常”之处在于函数在实际执行 IO 操作之前返回,更不用说完成了。实际执行是按不同的时间表完成的¹。

为了向“调用者”返回完成信号,异步调用通常采用一个完成处理程序,该处理程序被调用,并带有 IO 操作的结果。

在该完成处理程序中,通常会看到通信通道的结束或正在安排的下一个 IO 操作。这被称为异步调用链,并且在许多支持异步操作的语言中非常突出²

这需要一些时间来适应,但最终你会习惯这种模式。

考虑到这一点,重新审视其中一个提升样本,看看硬币是否下降:

Documentation sample Chat Client

  void handle_connect(const boost::system::error_code& error)
  
    if (!error)
    
      boost::asio::async_read(socket_,
          boost::asio::buffer(read_msg_.data(), chat_message::header_length),
          boost::bind(&chat_client::handle_read_header, this,
            boost::asio::placeholders::error));
    
  

  void handle_read_header(const boost::system::error_code& error)
  
    if (!error && read_msg_.decode_header())
    
      boost::asio::async_read(socket_,
          boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
          boost::bind(&chat_client::handle_read_body, this,
            boost::asio::placeholders::error));
    
    else
    
      do_close();
    
  

  void handle_read_body(const boost::system::error_code& error)
  
    if (!error)
    
      std::cout.write(read_msg_.body(), read_msg_.body_length());
      std::cout << "\n";
      boost::asio::async_read(socket_,
          boost::asio::buffer(read_msg_.data(), chat_message::header_length),
          boost::bind(&chat_client::handle_read_header, this,
            boost::asio::placeholders::error));
    
    else
    
      do_close();
    
  

  void do_write(chat_message msg)
  
    bool write_in_progress = !write_msgs_.empty();
    write_msgs_.push_back(msg);
    if (!write_in_progress)
    
      boost::asio::async_write(socket_,
          boost::asio::buffer(write_msgs_.front().data(),
            write_msgs_.front().length()),
          boost::bind(&chat_client::handle_write, this,
            boost::asio::placeholders::error));
    
  

  void handle_write(const boost::system::error_code& error)
  
    if (!error)
    
      write_msgs_.pop_front();
      if (!write_msgs_.empty())
      
        boost::asio::async_write(socket_,
            boost::asio::buffer(write_msgs_.front().data(),
              write_msgs_.front().length()),
            boost::bind(&chat_client::handle_write, this,
              boost::asio::placeholders::error));
      
    
    else
    
      do_close();
    
  

  void do_close()
  
    socket_.close();
  

异步操作的好处

异步 ​​IO 对于更基于事件的 IO 模型很有用。当扩展到大量 IO 操作时,它们还消除了第一个“天花板”。在传统的命令式代码模式中,许多客户端/连接需要许多线程才能同时为它们服务。但在实践中,线程无法扩展(因为典型服务器的逻辑 CPU 数量很少),这意味着 IO 操作会相互阻塞³。

使用异步 IO,您通常可以在单个线程上执行所有 IO 操作,从而大大提高效率 - 从而提高程序设计的某些方面(因为需要涉及的线程问题更少)。


¹ 存在许多选择,但假设 io_service::run() 在单独的线程上运行,这将导致 IO 操作被实际执行,并可能在需要时恢复并在该线程上完成

² 我会说 javascript 因这种模式而臭名昭著

³ 一个典型的例子是当远程过程调用在等待时保持线程被占用,例如要完成的数据库查询

【讨论】:

【参考方案2】:

这是我的看法:

关于递归

导致堆栈溢出的一种方法是让函数递归调用自身,从而使调用堆栈溢出。一组以循环方式相互调用的函数就相当于这样,所以是的,你的直觉是正确的。

算法的迭代版本,例如您描述的循环,可以防止这种情况发生。

现在,可以防止堆栈溢出的另一件事是存在可以针对尾递归进行优化的代码。尾递归优化需要编译器实现此功能。大多数主要编译器都实现了它。您提到的 Boost.Asio 功能似乎受益于这种优化。

关于代码设计

现在,C++ 实现了许多编程范式。这些范例也由许多其他编程语言实现。与您正在讨论的内容相关的编程范例是:

结构化编程 面向对象编程

从结构化编程的角度来看,您应该尽量强调代码重用,方法是将代码放入子例程中以最大限度地减少冗余代码。

从面向对象的角度来看,您应该以尽可能封装其逻辑的方式对类进行建模。

到目前为止,您提供的逻辑似乎已经足够封装,但是,您可能需要检查方法 writeread 是否应该保留 public,或者它们是否应该改为 private。尽量减少公共方法的数量有助于实现更高级别的封装。

【讨论】:

不,这里不涉及尾递归。如果不解决异步问题,您将无法回答有关异步控制流的问题。 (我理解是因为 OP 基本上对他的代码进行了绝育以删除它的迹象,但他肯定会在描述和标签中提到它。) 好点。我只是指的代码不是从原始 Asio 代码的角度来看的代码,而是从 OP 发布的代码。

以上是关于C++中类成员函数相互调用有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

详解c++中类的六个默认的成员函数

何时为 C++ 中的类的成员函数分配内存空间?

详解c++中类的六个默认的成员函数

详解c++中类的六个默认的成员函数

C++中类的静态数据成员函数解析

C++ 中类的默认成员函数的问题(构造函数、析构函数、运算符 =、复制构造函数)(默认 ctor、dtor、复制 ctor)