【问题标题】:std::move do not creating derived classstd::move 不创建派生类
【发布时间】:2017-04-22 18:32:16
【问题描述】:

我有一个容器,其中包含std::vector<std::shared_ptr<Base> > vec 中基类Base 的shared_ptr,这个容器也有函数add(Base&& base) - 它通过使用std::move 运算符将基对象添加到vec,但派生的移动构造函数不调用,仅调用了基本移动构造函数。

如何解决:我应该使用 dynamic_cast<Derived*>(&base) 将其转换为 Derived,然后使用 std::move

问题:我是否需要尝试强制转换为所有可能的派生类,然后使用std::move

class Base{
public:
    virtual ~Base(){}

    Base(const Base& base) = delete;
    Base(Base&& base){
        std::cout<<"base move\n";
    }
    Base(){}

    virtual void func(){
        std::cout<<"run func from BASE class\n";
    }
};
class Derived: public Base{
public:
    virtual ~Derived() = default;
    Derived(Derived&& derived):Base(std::move(derived)){
        std::cout<<"Derived move\n";
    }
    Derived():Base(){

    }

    virtual void func() override {
        std::cout<<"run func from DERIVED class\n";
    }
};
class Container{
    std::vector<shared_ptr<Base> > vec;
public:
    void add(Base&& base){
        this->vec.push_back(std::make_shared<Base>(std::move(base)));
    }
    void addDerived(Base&& base){
        //TRY ALL POSSIBLE CASTING???
        this->vec.push_back(std::make_shared<Derived>(std::move( *(dynamic_cast<Derived*>(&base)) )));
    }

    void print(){
        for(auto& obj: vec){
            obj->func();
        }
    }
};
int main() {
    std::cout << "Create container and add using function `add`" << std::endl;
    Container container;
    container.add(Derived());
    container.print();

    std::cout << "Create container and add using function `addDerived`" << std::endl;
    Container container_new;
    container_new.addDerived(Derived());
    container_new.print();
}
//Will print
Create container and add using function `add`
base move
run func from BASE class
Create container and add using function `addDerived`
base move
Derived move
run func from DERIVED class

【问题讨论】:

    标签: c++ inheritance c++14 move-constructor


    【解决方案1】:

    这是一个糟糕的界面:

    void add(Base&& base){
        this->vec.push_back(std::make_shared<Base>(std::move(base)));
    }
    

    您总是在创建一个Base。您正在切掉基础子对象,这不是真正的多态副本。为此,您必须在Base 上添加类似clone() 的方法。

    但是将多态性留给用户更简单。你的工作只是提供一个安全的界面。该界面应该是:

    void add(std::shared_ptr<Base> p) {
        vec.push_back(std::move(p));
    }
    

    现在,作为用户,我可以为我想要的任何派生类型提供shared_ptr,而无需您作为界面设计师担心:

    container.add(std::make_shared<MySuperCoolDerived>(42)); // cool
    

    【讨论】:

    • 谢谢!但是在当前上下文中,我无法从接口传递 shared_ptr 。我将值传递给构造函数中的向量,其中输入参数是 initializer_list。请检查我的更新!
    【解决方案2】:
    template<class D>
    std::enable_if_t< std::is_convertible<std::decay_t<D>*, Base*>::value >
    add(D&& d){
      add(std::make_shared<std::decay_t<D>>(std::forward<D>(d)));
    }
    void add(std::shared_ptr<Base> base){
      this->vec.push_back(std::move(base));
    }
    template<class D, class...Args>
    void emplace(Args&&...args){
      add(std::make_shared<D>(std::forward<Args>(args)...));
    }
    

    现在有 3 种添加方式。

    add(Derives()) 按您的意愿工作。 add(std::make_shared&lt;Foo&gt;(7)) 可以让你直接注入共享的ptrs。

    emplace&lt;Derived&gt;(args...) 让您可以就地构建它。

    插件有一个花哨的 SFINAE 检查;我跳过了它的位置。你可以做同样的可转换检查,如果 D 来自 Args,则 add 是可构造的......另外,当我编写 emplace 时,我有时会添加第一个参数是初始化列表构造函数,因为它修复了完美转发的常见缺陷。

    【讨论】:

    • 谢谢,add 方法完美运行!模板已经完成了它的工作。但是你能检查一下我在构造函数中关于 initializer_list 的更新吗?
    • @AlfredHichkog 如果您有新问题,请使用问题按钮而不是编辑按钮。
    【解决方案3】:

    您需要停止尝试移动到开始的对象中。如果您想要一个指向某个基类的指针容器,但您不希望用户直接分配您存储的派生类,那么应该由您的插入函数来进行分配。在这种情况下,它应该像典型的标准库 emplace 函数一样工作:

    template<typename T, typename ...Args>
    void add(Args && ...args)
    {
        static_assert(std::is_base_of<Base, T>::value, "The type must be a base class of `Base`");
        static_assert(std::is_convertible<T&, Base&>::value, "The type must be an *accessible* base class of `Base`");
        this->vec.push_back(std::make_shared<T>(std::forward<Args>(args)...));
    }
    
    ...
    
    container.add<Derived>();
    

    如果用户有一个Derived 实例,他们想要移动到由container 创建的新Derived,那么他们会执行container.add&lt;Derived&gt;(std::move(theirDerivedInstance));

    【讨论】:

    • 这是我对标准库感到奇怪的地方之一。 is_base_of 通常看起来是错误的,因为它也会匹配私有/模糊的基础......而您可能想要的是 is_convertible&lt;T*, Base*&gt;,它根本没有传达意图......
    • @Barry:两者都需要,因为某些东西可以在没有实际基础的情况下进行转换。
    • @NicolBolas 如果您检查指针而不是参考,则不会。
    • @T.C.:很公平,但是这样,您可以更直接地为用户诊断问题。您可以区分用户未将其设为基类与用户未将其设为可访问基类之间的区别。
    • @NicolBolas 也许需要std::is_public_base_of。虽然现在我写了它,这看起来很糟糕。甚至公众也是不够的。布莱格。
    猜你喜欢
    • 1970-01-01
    • 2012-09-13
    • 2020-05-23
    • 2014-03-22
    • 1970-01-01
    • 2013-01-12
    • 1970-01-01
    • 2023-01-26
    • 2015-08-10
    相关资源
    最近更新 更多