c – 出乎意料地能够从基类ctor调用派生类虚函数
谁能帮助解释这种意想不到的行为?
前提 我创建了包含成员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; } 问题 您可能已经注意到,这里有一个微妙的错误:Thread :: ThreadStart(…)是从Thread ctor上下文调用的,因此调用pure / virtual函数不会调用派生类的实现.运行时错误证实了这一点: 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的调用似乎具有能够从基类`构造函数上下文调用派生类’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已初始化.操作系统安排它在未来的某个时刻开始. 如果在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)); } } 主要: for(int i = 0; i < 10000; ++i){ A a(42); a.join(); } demo (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |