c++ 如何为基类引用传递的值创建unique_ptr?

xyhw6mcr  于 2023-04-01  发布在  其他
关注(0)|答案(3)|浏览(171)

我有BaseDerived类,我需要通过虚成员函数foo()从它们获得多态行为:

#include <iostream>
#include <memory>

class Base {
public:
    virtual int foo() const = 0;
};

class Derived : public Base {
public:
    int foo() const override { return 42; }
};

std::unique_ptr<Base> clone(Base & base) {
    //auto copy = base;  // cannot create an abstract type
    //return std::make_unique(copy);

    return std::make_unique(base);
}

int main() {
    auto d = Derived();

    auto p = clone(d);
 
    std::cout << p->foo() << '\n';
}

不编译:https://godbolt.org/z/voaGdf1sM

<source>: In function 'std::unique_ptr<Base> clone(Base&)':
<source>:19:28: error: no matching function for call to 'make_unique(Base&)'
   19 |     return std::make_unique(base);
      |            ~~~~~~~~~~~~~~~~^~~~~~

clone()之外,我希望能够在程序的大部分时间内使用值语义来处理Derived的示例,但有时需要将Derived的示例添加到集合中(std::vector<std::unique_ptr<Base>>)并保持多态性,所以在本例中,我需要能够将unique_ptr s添加到Derived对象的新 * 副本 * 中。因此,我有一个函数clone(),它引用一个Derived对象,并打算制作一个副本,然后由unique_ptr拥有,这是返回的。
不幸的是,我无法解决如何对像Base这样的抽象类型的引用进行多态复制。
我不能通过值传递参数,然后将隐式副本移动到unique_ptr中,因为不可能有Base的示例,而且我需要多态性。
我研究了使用转发引用,类似于:

std::unique_ptr<Base> clone(Base && base) {
    return std::make_unique(base);
}

// ...
auto p = clone(std::move(d));

但是我不想在调用者那里公开所有权,并要求他们调用std::move-它应该可以自由地传入对现有对象的引用,并期望clone复制它,而不是获得它的所有权。我实际上 * 希望 * 它被复制,并且源对象保持完整并能够再次使用(例如,制作更多副本)。
注意:如果我能让它工作,clone最好使用const Base &,因为我毕竟是出于性能原因通过引用传递的。
我在这里尝试做的是-制作一个副本,作为unique_ptr进行管理,而不对调用者施加移动语义-甚至可能吗?

uyhoqukh

uyhoqukh1#

我觉得你在找这样的东西:

#include <iostream>
#include <memory>

class Base {
public:
    virtual ~Base () { }
    virtual int foo() const = 0;
    virtual std::unique_ptr <Base> clone (void) const = 0;
};

class Derived : public Base {
public:
    int foo() const override { return 42; }

    std::unique_ptr <Base> clone (void) const override
    {
        auto result = std::make_unique <Derived> ();
        *result = *this;
        return result;
    }
};

int main() {
    auto d = Derived ();
    auto p = d.clone ();
    std::cout << p->foo() << '\n';
}

我不知道还有什么可说的,但你也可以这样做:

Base &base = d;
...
auto p = b.clone ();

我觉得这更接近你的要求
Live demo
编辑:根据@eerorika的评论,在Base中添加了一个虚拟析构函数,我省略它是多么草率!

vq8itlhq

vq8itlhq2#

Paul's solution中不幸的潜在重复可以通过使用Curiously recurring template pattern来缓解:

template <class T>
class TBase : public Base {  // Base from Paul's answer
public:
    std::unique_ptr<Base> clone() const override {
        const T& ref = static_cast<const T&>(*this);
        return std::make_unique<T>(ref);
    }
};

class Derived1 : public TBase<Derived1> {
public:
    int foo() const override { return 42; }
};

class Derived2 : public TBase<Derived2> {
public:
    int foo() const override { return 1337; }
};
k3bvogb1

k3bvogb13#

我在SO的其他地方遇到的一个部分解决方案是使用模板化的clone()函数来处理具体类型:

#include <iostream>
#include <memory>

class Base {
public:
    virtual int foo() const = 0;
};

class Derived : public Base {
public:
    int foo() const override { return 42; }
};

template <typename T>
std::unique_ptr<Base> clone(T const & t) {
    return std::make_unique<T>(t);
}

int main() {
    auto d = Derived();

    auto p = clone(d);
 
    std::cout << p->foo() << '\n';
}

https://godbolt.org/z/GbTz4nb3E
这使得make_unique可以像调用Derived一样调用所提供参数的复制构造函数,因为调用者知 prop 体类型。不过,如果调用者只有Base的引用或指针,我不确定这是否仍然有效。

相关问题