出乎意料地能够从基类ctor调用派生类虚函数

时间:2016-10-29 09:01:46

标签: c++ c++11 inheritance pure-virtual stdthread

任何人都可以帮助解释这种意外行为吗?

前提

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

守则

#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;
}

问题

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

pure virtual method called
terminate called without an active exception
Aborted

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

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的调用,似乎可以调用派生类&#39;来自基类`构造函数上下文的纯/虚函数!这与先前的学习和经验并不一致。

在Windows 10上的Cygwin x64中构建环境.gcc版本是:

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.

我对这种观察感到困惑,并且好奇地发生了什么。谁能摆脱光明?

1 个答案:

答案 0 :(得分:9)

由于竞争条件,此程序的行为未定义。

但是,如果你想推理它,那就试试吧。

对于A的构造,这是发生的事情:

  1. mThread已初始化。操作系统安排它在未来的某个时刻开始。
  2. std::cout << __PRETTY_FUNCTION__ << std::endl; - 从程序的角度来看,这是一个相当慢的操作。

  3. A构造函数运行 - 初始化其 vtable (这不是stanard的强制要求,但据我所知,所有实现都会这样做)。

    如果在mThread计划启动之前发生这种情况,您将获得您观察到的行为。否则,您将获得纯虚拟呼叫。

  4. 由于这些操作没有以任何方式排序,因此行为未定义。

    你可以注意到你从你的base的构造函数中删除了一个相当慢的操作,因此初始化你的派生 - 及其vtable - 要快得多。比如说,在操作系统实际安排mThread的线程开始之前。话虽这么说,这并没有解决问题,只是不太可能遇到它。

    如果稍微修改一下你的例子,你会注意到删除IO代码会使竞争变得更难找,但是没有修复。

    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));
        }
    }
    

    主:

    for(int i = 0; i < 10000; ++i){
        A a(42);
        a.join();
    }
    

    demo