首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >意外地能够从基类ctor调用派生类虚拟函数。

意外地能够从基类ctor调用派生类虚拟函数。
EN

Stack Overflow用户
提问于 2016-10-29 09:01:45
回答 1查看 419关注 0票数 6

有人能帮我解释一下这种意想不到的行为吗?

前提

我已经创建了包含成员std::thread变量的类线程。线程的ctor构造成员std::thread,提供指向调用纯虚拟函数(由基类实现)的静态函数的指针。

代码

代码语言:javascript
复制
#include <iostream>
#include <thread>
#include <chrono>

namespace
{

class Thread
{
public:
    Thread()
        : mThread(ThreadStart, this)
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl; // This line commented later in the question.
    }

    virtual ~Thread() { }

    static void ThreadStart(void* pObj)
    {
        ((Thread*)pObj)->Run();
    }

    void join()
    {
        mThread.join();
    }

    virtual void Run() = 0;

protected:
    std::thread mThread;
};

class Verbose
{
public:
    Verbose(int i) { std::cout << __PRETTY_FUNCTION__ << ": " << i << std::endl; }
    ~Verbose() { }
};

class A : public Thread
{
public:
    A(int i)
        : Thread()
        , mV(i)
    { }

    virtual ~A() { }

    virtual void Run()
    {
        for (unsigned i = 0; i < 5; ++i)
        {
            std::cout << __PRETTY_FUNCTION__ << ": " << i << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }

protected:
    Verbose mV;
};

}

int main(int argc, char* argv[])
{
    A a(42);
    a.join();

    return 0;
}

问题

正如您可能已经注意到的,这里有一个微妙的bug:Thread::ThreadStart(...)是从Thread ctor上下文中调用的,因此调用纯/虚拟函数不会调用派生类的实现。运行时错误证明了这一点:

代码语言:javascript
复制
pure virtual method called
terminate called without an active exception
Aborted

但是,如果在Thread ctor中删除对std::cout的调用,则会出现意外的运行时行为:

代码语言:javascript
复制
virtual void {anonymous}::A::Run(){anonymous}::Verbose::Verbose(int): : 042

virtual void {anonymous}::A::Run(): 1
virtual void {anonymous}::A::Run(): 2
virtual void {anonymous}::A::Run(): 3
virtual void {anonymous}::A::Run(): 4

也就是说,在std::cout ctor中删除对Thread的调用,似乎可以从基类构造函数上下文中调用派生类‘纯/虚拟函数!这与以前的学习和经验不一致。

在Windows 10上用Cygwin x64构建环境。gcc版本是:

代码语言:javascript
复制
g++ (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

我对这一观察感到困惑,对正在发生的事情充满好奇。有人能说出真相吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-10-29 09:10:13

由于种族条件,此程序的行为未定义。

但是,如果你想推理的话,我们试试吧。

对于A的建筑,下面是发生的事情:

  1. 初始化mThread。操作系统计划在将来的某个时候开始。
  2. std::cout << __PRETTY_FUNCTION__ << std::endl; --从程序的角度来看,这是一个相当缓慢的操作。
  3. A构造函数运行-初始化它的vtable (这不是标准要求的,但据我所知,所有实现都是这样做的)。 如果在计划启动mThread之前发生了这种情况,您将得到所观察到的行为。否则,您将得到纯虚拟调用。

因为这些操作没有以任何方式进行排序,所以行为是没有定义的。

您可以注意到,您从基的构造函数中删除了一个相当慢的操作,因此初始化派生(以及它的vtable )的速度要快得多。比方说,在操作系统真正安排mThread线程启动之前,这并没有解决问题,只是减少了遇到问题的可能性。

如果你稍微修改一下你的例子,你会发现删除IO代码会让竞争更加困难,但什么都没有解决。

代码语言:javascript
复制
virtual void Run()
{
    for (unsigned i = 0; i < 1; ++i)
    {
        std::cout << __PRETTY_FUNCTION__ << ": " << i << std::endl;
//      std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

主要:

代码语言:javascript
复制
for(int i = 0; i < 10000; ++i){
    A a(42);
    a.join();
}

demo

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40317551

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档