是否可以使用其基类构造函数创建派生类的指针而不修改派生类的布局?

Posted

技术标签:

【中文标题】是否可以使用其基类构造函数创建派生类的指针而不修改派生类的布局?【英文标题】:is it possible to create pointers of derived classes using their base class constructor without modifying the layout of the derived classes? 【发布时间】:2021-12-10 10:37:49 【问题描述】:

考虑这种布局:

#include <memory>
#include <vector>

struct data;

struct task 
    data* data_ptr = nullptr;

    virtual void work() = 0;
;

struct special_task : task 

    void work() override  /*work with the data*/ 
;

task 可以访问数据。派生类必须实现它们各自的work() 例程。

现在task_collection 存储数据和任务指针向量,派生类的实例可以添加到该向量:

struct task_collection 
    data data;
    std::vector<std::unique_ptr<task>> tasks;

    template<typename T>
    void add() 
        this->tasks.push_back(std::make_unique<T>());
        this->tasks.back()->data_ptr = &this->data;
    
;

int main() 
    task_collection t;
    t.add<special_task>();

现在这很好用。但是,我想知道是否可以将 data* data_ptr 替换为参考,因为它在这里似乎更合适,并且它也将许多 -&gt; 替换为 .s


但是,如果不更改 special_task 的布局,似乎不可能实现这一点,因为引用需要基类 task 中的构造函数,而派生类会取消其基构造函数:

struct task 
    task(data& data) : data_ref(data)
    data& data_ref;

    virtual void work() = 0;
;

struct special_task : task 

    void work() override  /*work with the data*/ 
;

struct task_collection 
    data data;
    std::vector<std::unique_ptr<task>> tasks;

    template<typename T>
    void add() 
        this->tasks.push_back(std::make_unique<T>(this->data)); //Error!
    
;

它给出了这个错误:

Error   C2664   'special_task::special_task(const special_task &)': cannot convert argument 1 from 'data' to 'const special_task &'

我知道这可以通过添加来“解决”

using task::task;

到每个派生类,因为现在它找到了适当的构造函数。但这并不是真正的解决方案,因为可能有数百个派生类,由多人编写。如果using task::task; 的一个实例丢失,可能会导致头痛。额外的代码行也抵消了使用引用而不是指针带来的好处。


那么有没有办法将task::data_ref 实现为仅修改task_collectiontask 而没有任何派生类的引用?

【问题讨论】:

如果没有 using 声明,我认为你不能做你目前正在做的事情。但是,您可以稍微改变您的任务概念。怎么样:工作(数据和数据)而不是工作()?那么你根本不需要数据作为实例变量,只需在调用任务时传递数据即可。 您的主要问题是用. 替换-&gt; 吗?您是否接受将 -&gt; 替换为 .get(). 或者您无法接受? @Wutz 如果每个任务都可以拥有自己对数据的引用,那么会有很大的好处,因为除了work() 之外还有更多功能。它们也被传递给其他函数,它们应该在没有额外的 data 参数的情况下独立工作。 @StackDanny 你看到我的问题了吗? 如果使用task::task的一个实例;缺少它可能会导致头疼。 指出哪个类缺少using data::data; 的清晰而简单的编译时错误真的令人头疼吗? 【参考方案1】:

ITNOA

如果问题的所有者可以接受一些时间使用get() 而不是. 来访问数据,他可以使用std::reference_wrapper 而不是原始引用来解决重新绑定问题。

#include <memory>
#include <vector>
#include <functional>

struct data;

static data empty_data;

struct task 
    std::reference_wrapper<data> data_ptr = empty_data;

    virtual void work() = 0;
;

struct special_task : task 

    void work() override  /*work with the data*/ 
;

struct task_collection 
    data data;
    std::vector<std::unique_ptr<task>> tasks;

    template<typename T>
    void add() 
        this->tasks.push_back(std::make_unique<T>());
        this->tasks.back()->data_ptr = std::ref(this->data);
    
;

int main()

    task_collection t;
    t.add<special_task>();

为了使用数据,你可以像下面这样表现

data temp_data = t.tasks.back()->data_ptr;

正如您在How to correctly use std::reference_wrappers 中看到的那样,std::reference_wrapper 类实现了一个隐式转换运算符到 T&:

constexpr operator T& () const noexcept;

所以当需要 T(或 T&)时调用隐式运算符,而您不需要使用 get() 函数,例如

void f(some_type x);
// ...
std::reference_wrapper<some_type> x;
some_type y = x; // the implicit operator is called
f(x);            // the implicit operator is called and the result goes to f.

所以你有时不得不使用.get(). 而不是总是使用-&gt;

【讨论】:

我想这是一个品味问题,但我发现 .get() 比 -> 糟糕得多。

以上是关于是否可以使用其基类构造函数创建派生类的指针而不修改派生类的布局?的主要内容,如果未能解决你的问题,请参考以下文章

虚函数和基类中的this指针的问题!

包含指向派生模板类的基类指针的类的赋值运算符和复制构造函数

什么是构造函数继承?

生成一个派生类对象时,调用基类和派生类构造函数按啥次序

继承

阿里笔试题-派生类构造函数 创建顺序