意外地能够从基础 class 构造函数调用派生的-class 虚函数
Unexpectedly able to call derived-class virtual function from base class ctor
任何人都可以帮助解释这种意外行为吗?
前提
我创建了 class 包含成员 std::thread
变量的线程。 Thread 的 ctor 构造成员 std::thread
提供指向调用纯虚函数的静态函数的指针(由基 classes 实现)。
代码
#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(...)
是从 Thread
ctor 上下文中调用的,因此调用 pure/virtual 函数不会调用派生的 class' 执行。运行时错误证实了这一点:
pure virtual method called
terminate called without an active exception
Aborted
但是,如果我在 Thread
ctor:
中删除对 std::cout
的调用,则会出现意外的运行时行为
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
即在 Thread
ctor 中删除对 std::cout
的调用似乎具有能够从基础 class 调用派生的 class' pure/virtual 函数的效果构造函数上下文!这与之前的学习和经验不符。
在 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.
我对这个观察感到困惑,并对正在发生的事情充满好奇。任何人都可以阐明吗?
由于竞争条件,此程序的行为未定义。
但是,如果你想推理它,让我们试试。
对于 A
的构造,会发生以下情况:
mThread
已初始化。 OS 安排它在未来的某个时间点开始。
std::cout << __PRETTY_FUNCTION__ << std::endl;
- 从程序的角度来看,这是一个相当慢的操作。
A
构造函数运行 - 初始化它的 vtable (这不是标准强制要求的,但据我所知,所有实现都这样做这个)。
如果这发生在 mThread
计划开始之前,您将获得观察到的行为。否则,您将获得纯虚拟调用。
因为这些操作没有任何顺序,所以行为是未定义的。
您会注意到您从基类的构造函数中删除了一个相当慢的操作,从而更快地初始化您的派生 - 及其 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));
}
}
主要内容:
for(int i = 0; i < 10000; ++i){
A a(42);
a.join();
}
任何人都可以帮助解释这种意外行为吗?
前提
我创建了 class 包含成员 std::thread
变量的线程。 Thread 的 ctor 构造成员 std::thread
提供指向调用纯虚函数的静态函数的指针(由基 classes 实现)。
代码
#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(...)
是从 Thread
ctor 上下文中调用的,因此调用 pure/virtual 函数不会调用派生的 class' 执行。运行时错误证实了这一点:
pure virtual method called
terminate called without an active exception
Aborted
但是,如果我在 Thread
ctor:
std::cout
的调用,则会出现意外的运行时行为
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
即在 Thread
ctor 中删除对 std::cout
的调用似乎具有能够从基础 class 调用派生的 class' pure/virtual 函数的效果构造函数上下文!这与之前的学习和经验不符。
在 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.
我对这个观察感到困惑,并对正在发生的事情充满好奇。任何人都可以阐明吗?
由于竞争条件,此程序的行为未定义。
但是,如果你想推理它,让我们试试。
对于 A
的构造,会发生以下情况:
mThread
已初始化。 OS 安排它在未来的某个时间点开始。std::cout << __PRETTY_FUNCTION__ << std::endl;
- 从程序的角度来看,这是一个相当慢的操作。A
构造函数运行 - 初始化它的 vtable (这不是标准强制要求的,但据我所知,所有实现都这样做这个)。如果这发生在
mThread
计划开始之前,您将获得观察到的行为。否则,您将获得纯虚拟调用。
因为这些操作没有任何顺序,所以行为是未定义的。
您会注意到您从基类的构造函数中删除了一个相当慢的操作,从而更快地初始化您的派生 - 及其 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));
}
}
主要内容:
for(int i = 0; i < 10000; ++i){
A a(42);
a.join();
}