假设我们有经典的基类和派生类,像这样
class B {
public:
virtual ~B() {
// calling it here is too late, see explanations
//common_pre_cleanup_function();
}
void common_pre_cleanup_function() { }
};
class D : public B {
public:
virtual ~D() {
// What if we forget to do this call in another derived class?
common_pre_cleanup_function();
}
};
在销毁common_pre_cleanup_function()
的成员之前,如何确保在所有派生的D
的析构函数中调用类似D
的函数,而不必在每个析构函数中显式调用此函数-实施新的D
?
在我当前的项目中,我们有一个基类,该基类实现某些并行性和线程功能,并最终启动一个新线程来完成实际工作。 在此基类的析构函数中,我们想要确保线程始终停止并联接,以便正确清理线程。
但是,派生类可以创建该线程在基类中使用的成员。因此,如果我们销毁派生类的对象,这些成员也将被销毁。但是此时,由基类管理的线程仍然可以运行,并且现在错误地访问了被破坏的成员。
我知道,这不是解决问题的最聪明方法,可能将线程/并行化部分和“实际工作”部分拆分为单独的类可能是更聪明的主意。但是我很感兴趣是否有任何方法不涉及对现有代码库的完全重写。
这里的代码更接近我们的情况
class BackgroundTask {
public:
virtual ~BackgroundTask() {
// if we forget to call stop() in the derived classes, we will
// at this point have already destroyed any derived members
// while the thread might still run and access them; so how/where
// can we put this call?
//stop();
}
void stop() {
cancelFlag_.set();
thread_.join();
}
// more functions helping with Background tasks
private:
Thread thread_;
Condition cancelFlag_;
};
class MyTask : public BackgroundTask {
public:
virtual ~MyTask() {
// with the current case, we have to remember to call
// this function in all destructors in classes derived
// from BackgroundTask; that's what I want to avoid
stop();
}
private:
std::unique_ptr<MyClass> member;
};
答案 0 :(得分:1)
您完全不知道。在这种情况下,最好的办法是重新设计一切工作方式,以防止出现问题。
但是面对现实吧,您很可能没有时间和/或资源来实现这一目标。因此(我认为),您的第二个最佳选择是确保对派生类的销毁成员的任何调用都会立即将您的应用程序杀死,并显示一条非常清晰的错误消息。
如果系统必须发生故障,请尽早故障。
答案 1 :(得分:1)
您可能会做类似的事情:
template <typename TaskImpl>
class Task final : public TaskImpl
{
static_assert(std::is_base_of<BackgroundTask, TaskImpl>);
public:
virtual ~Task() { stop(); }
};
然后
class MyTaskImpl : public BackgroundTask
{
// ...
private:
std::unique_ptr<MyClass> member;
};
using MyTask = Task<MyTaskImpl>;
答案 2 :(得分:0)
虽然我同意设计存在缺陷的评论.....
假设对象是动态分配的,一种解决方案是制作析构函数virtual
和protected
,并使用一个单独的函数在销毁对象之前负责调用“ pre-cleanup” 。例如;
class B
{
public:
void die()
{
common_pre_cleanup_function();
delete this;
};
protected:
virtual ~B() {};
private:
void common_pre_cleanup_function() { };
};
class D : public B
{
protected:
virtual ~D() {};
};
int main()
{
B *b = new D;
b->die();
}
这对班级用户有一些限制。特别是,如果
new
表达式创建对象; die()
后会调用对象的任何非静态成员函数die()
后可以访问任何非静态数据成员这也意味着,如果您维护一组对象(如指针向量B*
),则有必要从列表中删除该指针,以确保该对象死亡后不再使用
protected
析构函数可以防止某些事情。不是friend
或B
的{{1}}成员的函数不能;
D
或B
的自动存储期限D
。例如,上面delete
中的语句delete b;
将不会编译。这还可以防止在调用“ pre-cleanup”之前破坏对象。答案 3 :(得分:-1)
编辑:我意识到这并不能解决您的问题,但请留在此处以供参考。
如前所述,每个对象都应负责管理自己的资源,因此您的设计从一开始就存在一些缺陷。
请考虑以下示例。 TaskRunner
负责启动一个线程,并在调用构造函数时将其关闭(教科书RAII)。 Task
类通过纯虚拟继承来指定任务生存期内的操作。
#include <atomic>
#include <future>
#include <iostream>
#include <memory>
struct Task {
virtual void run( ) = 0;
virtual ~Task( ) {
}
};
class TaskRunner final {
std::unique_ptr<Task> task;
std::future<void> fut;
std::atomic<bool> terminate;
public:
TaskRunner(std::unique_ptr<Task>&& task)
: task {std::move(task)}
, terminate {false} {
fut = std::async(std::launch::async, [this] {
while(!terminate) {
this->task->run( );
}
this->task.reset( );
});
}
TaskRunner(TaskRunner&&) = delete;
TaskRunner& operator=(TaskRunner&&) = delete;
TaskRunner(const TaskRunner&) = delete;
TaskRunner& operator=(const TaskRunner&) = delete;
~TaskRunner( ) {
terminate = true;
fut.wait( ); // Block until cleanup is completed
std::cout << "~TaskRunner()" << std::endl;
}
};
struct MyTask : public Task {
int i = 0;
void
run( ) {
// Do important stuf here, don't block.
std::cout << "MyTask::run() " << i++ << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds {100});
}
~MyTask( ) override {
// Clean up stuff here, run() is guaranteed to never be run again
std::cout << "~MyTask()" << std::endl;
}
};
int
main( ) {
TaskRunner t {std::make_unique<MyTask>( )};
std::this_thread::sleep_for(std::chrono::seconds {1});
}
输出
MyTask::run() 0
MyTask::run() 1
MyTask::run() 2
MyTask::run() 3
MyTask::run() 4
MyTask::run() 5
MyTask::run() 6
MyTask::run() 7
MyTask::run() 8
MyTask::run() 9
~MyTask()
~TaskRunner()