Sto*_*row 6 c++ inheritance pure-virtual c++11 stdthread
谁能帮助解释这种意想不到的行为?
前提
我创建了包含成员std::thread变量的类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;
}
Run Code Online (Sandbox Code Playgroud)
问题
您可能已经注意到,这里有一个微妙的错误:Thread::ThreadStart(...)从Threadctor上下文调用,因此调用pure/virtual函数不会调用派生类的实现.运行时错误证实了这一点:
pure virtual method called
terminate called without an active exception
Aborted
Run Code Online (Sandbox Code Playgroud)
但是,有意外的运行时的行为,如果我删除调用std::cout的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
Run Code Online (Sandbox Code Playgroud)
即删除调用std::cout的Thread构造函数似乎有能够从基础class`构造背景下调用派生类的纯/虚函数的效果!这与先前的学习和经验不一致.
在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.
Run Code Online (Sandbox Code Playgroud)
我对这种观察感到困惑,并且对于正在发生的事情充满了好奇心.谁能摆脱光明?
由于竞争条件,此程序的行为未定义.
但是,如果你想推理它,试试吧.
对于A建设,这是发生的事情:
mThread已初始化.操作系统安排它在未来的某个时刻开始.std::cout << __PRETTY_FUNCTION__ << std::endl; - 从程序的角度来看,这是一个相当慢的操作.
A构造函数运行 - 初始化它的vtable(这不是stanard的强制要求,但据我所知,所有实现都会这样做).
如果在mThread计划开始之前发生这种情况,您将获得您观察到的行为.否则,您将获得纯虚拟呼叫.
由于这些操作没有以任何方式排序,因此行为未定义.
你可以注意到你从你的base的构造函数中删除了一个相当慢的操作,因此初始化你的派生 - 以及它的vtable - 要快得多.比如说,在OS实际安排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));
}
}
Run Code Online (Sandbox Code Playgroud)
主要:
for(int i = 0; i < 10000; ++i){
A a(42);
a.join();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
328 次 |
| 最近记录: |