【问题标题】:C++ - calling derived function from abstract base pointerC++ - 从抽象基指针调用派生函数
【发布时间】:2011-04-01 09:00:10
【问题描述】:

我一直在尝试创建基于继承的 TCP 服务器模型,并取得了不同程度的成功。这些服务器由一个单例管理,其任务是关闭这些服务器和其他简单的维护功能:

class TCPServer {
public:
    TCPServer();
    ~TCPServer();

    void Bind(TCPDaemon *daemon) {
        if(!daemon->IsRunning()) {
            throw TCPBindException("Daemon is inactive");
        }

        // if the port is not taken, bind this daemon to it
        if(this->servers.count(daemon->port())==0) {
            this->servers[daemon->port()]=daemon;
            ...
        } else {
            throw TCPBindException("Port is taken");
        }
    }

    void Shutdown() {
        MASON::UINT16 i;
        for(i=0;i<this->servers.size();i++) {
            this->Shutdown((*this->servers.begin()).first);
        }
    }

    void Shutdown(unsigned short port)  {
        if(this->servers.count(port)) {

            if(this->servers[port]->IsRunning()) {
                this->servers[port]->Stop();
            }

            delete this->servers[port];
            this->servers.erase(port);

        }
    }

private:
    std::map<unsigned short, TCPDaemon*> servers;

};

TCPDaemon 类的 Stop() 函数是一个纯虚函数。我的问题是,当调用 Shutdown() 函数时,它试图调用这个纯虚拟而不是派生类的版本。我怎样才能强迫它做正确的事?

提前致谢

[edit] 抱歉,我之前没有包含 TCPDaemon 代码,它派生自 TCPSocket 类(我已经检查过它是 100% 工作的,并且相当不言自明)。这里是:

class TCPDaemon: public TCPSocket {
public:
    TCPDaemon(unsigned short port) {
        this->_enabled=false;
        this->_host.ipaddr(INADDR_ANY);
        this->_host.port(port);
        this->paused=false;

        struct sockaddr_in opts=this->_host.Compile();

        #ifdef PLATFORM_WINDOWS
            WSADATA wsaData;
            if(WSAStartup(0x0202, &wsaData)) {
                throw TCPDaemonException("Failed to start WSA");
            }
        #endif

        this->raw_socket=socket(AF_INET, SOCK_STREAM, 0);
        if(this->raw_socket<=0) {
            throw TCPDaemonException("Failed to create socket");
        }

        if(int status=bind(this->raw_socket, (sockaddr*)&opts, sizeof(sockaddr))) {
            printf("error [%i]\r\n", status);
            throw TCPDaemonException("Failed to bind to port");
        }

        if(listen(this->raw_socket, 5)) {
            throw TCPDaemonException("Failed to listen on port");
        }

        this->_enabled=true;

    }

    virtual ~TCPDaemon() {
        this->Shutdown();
    }

    virtual void Start()=0;
    virtual void Run(TCPSocket*)=0;
    virtual void Stop()=0;

    unsigned short port() {
        return this->host().port();
    }

    bool IsRunning() {
        return this->_enabled;
    }

    TCPSocket *Accept() {
        SOCKET client;
        struct sockaddr client_addr;
        int len=sizeof(client_addr);
        client=accept(this->raw_socket, &client_addr, &len);

        return new TCPSocket(client, &client_addr);
    }

    void Shutdown() {

    }

private:
    bool _enabled;
    bool paused;

};

这里是一个示例派生服务器及其创建方法:

   class EchoServer: public TCPDaemon {
    public:
        EchoServer(MASON::UINT16 port): TCPDaemon(port) {
        }

        ~EchoServer() {}

        virtual void Start() {

        }

        virtual void Run(TCPSocket *client) {
            printf("RUN\r\n");
            Accessor<TCPSocket> acc_client=client;
            acc_client->Write(Accessor<Blob> (new Blob(std::string("hello!"))));
            acc_client->Disconnect();
        }

        virtual void Stop() {

        }

    };

myTCPServer->Bind(new EchoServer(8008));

[edit+1] 我认为问题归结为这个(我很容易出错): 我有一个基类 TCPDaemon 的 std::map,它有一个纯虚拟/抽象函数 Stop()。看来,当我通过映射中的条目之一调用 Stop() 时,它正在尝试调用 TCPDaemon::Stop(),而不是覆盖函数 EchoServer::Stop()。这可能是问题吗?如果是这样,我该如何解决?

【问题讨论】:

  • 您能给我们提供TCPDaemon 的代码,它失败的地方,而不是调用失败代码的函数吗?
  • 我认为我们需要查看 TCPDaemon 的相关部分及其派生类之一才能对您有所帮助。
  • TCPDaemon 是基类还是派生类?显示基础和派生的定义。
  • 我的猜测是您正在使用错误类的实例调用 Bind。即一个没有实现 Stop() 的类,我们可以看看更多的代码吗
  • TCPSocket中是否有Stop?

标签: c++ inheritance pure-virtual


【解决方案1】:

检查你声明的语法:

class TCPDaemon
{
    virtual void stop() = 0;
};

class MyDaemon : public TCPDaemon
{
    virtual void stop()
    {
        //Do stuff here.
    }
};

这是我在没有更多代码的情况下能做的最好的事情。

编辑:

好的,看来您正在使用抽象函数。下一个问题是:你得到的错误是什么?我可以肯定地说,它不是试图从 TCPDeamon 调用 Stop()。那是不可能的,因为它甚至没有实现。

【讨论】:

  • 你的代码是正确的,但你不正确的是它不是一个纯虚函数;在您的代码中, TCPDaemon::stop 一个纯虚函数。 OP的代码不是。
  • 好像我的术语搞砸了。谢谢你的收获。
【解决方案2】:

多亏了我收到的意见,我终于解决了。问题在于 TCPServer::Shutdown(unsigned short) 中的删除调用,这导致代码的完全不同部分发生内存访问冲突......一个相当愚蠢的错误,我将尽快将智能指针包装在所有内容周围。

感谢您的所有反馈!

【讨论】:

  • 如果你从 TCPDaemon::Shutdown() 调用 Stop(),并且从 TCPDaemon::~TCPDaemon 调用,那么我很高兴 vtbl 将被拆除到它试图去的地方调用 TCPDaemon::Stop() 而不是您的派生版本。你必须非常小心你在构造函数或析构函数中调用什么,因为你会得到你的类的版本的虚函数。
  • 这就是我最初在做的事情,在我发布这个问题的几个小时前,但通过研究我发现了你刚刚解释的效果,并将 Stop() 移到 TCPServer 中以进行良好的测量。一个教训肯定是艰难的!
  • 顺便说一句 - 如果你是菜鸟,那么代码很好。看看使用 boost_shared_ptr 而不是弄乱原始指针。另外 - 为什么不在 c# 中这样做,更不痛苦
【解决方案3】:

我不确定这是否是您看到的问题,但以下函数肯定存在问题。

void Shutdown() {
    MASON::UINT16 i;
    for(i=0;i<this->servers.size();i++) {
        this->Shutdown((*this->servers.begin()).first);
    }
}

在每个循环迭代器之后,i 将增加 1 并且 size 将减少,因此您只会关闭一半的服务器。也许保持一些开放是导致更多错误的原因。

你可以这样做:

void Shutdown() {
    MASON::UINT16 i;
    std::size_t nrServers = this->servers.size();
    for(i=0;i<nrServers;i++) {
        this->Shutdown((*this->servers.begin()).first);
    }
}

或者,我更喜欢它,因为它更好地显示了代码的意图:

void Shutdown() {
    while (!this->servers.empty()) {
        this->Shutdown((*this->servers.begin()).first);
    }
}

【讨论】:

  • 但 i 没有被用作索引 - 只是一个计数器
  • @pm100 这无关紧要,它用于确定何时退出循环。假设您有 4 台服务器,经过一次循环迭代,i 将为 1,size() 将为 3,接着,i 将为 2,size() 将为 2。循环将终止,留下 2服务器“活着”
【解决方案4】:

如果你说它调用纯虚方法而不是派生类的版本,那意味着你的派生类调用它。例如

class t1
{
 public:
virtual ~t1(){};
virtual void foo()=0        
  {
    std::cout << "pure virtual";
};
};

class t2:public t1
{
public :

virtual void foo() 
    {
    t1::foo();
    std::cout << "derived class ";
};
};

据我所知,只有派生类的对象可以调用基类的纯虚函数(当然,如果它在基类中实现)

【讨论】:

  • 或者他只是在某个地方忘记了virtual,或者误用了“纯虚拟”一词
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-17
  • 2010-12-24
相关资源
最近更新 更多