如何创建一个 unique_ptr,指向通过基类引用传递的值?

4

我有一个名为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>>)并保留多态性,因此在这种情况下,我需要能够向新的副本Derived对象添加unique_ptrs。因此,我有一个函数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的副本,而不会对调用者施加移动语义 - 是否可能?


1
“create” 不是一个制作副本的函数的好名称。“copy”或“clone”会更好。 - 463035818_is_not_a_number
是的,这是一个很好的观点,我完全同意。我最初创建了一个 std::vector<unique_ptr<Base>> 并返回它,但我将其剥离以使其更简单,并无意中留下了名为“create”的函数。编辑:我已经在我的问题中将“create”重命名为“clone”。 - davidA
@JaMiT,那些链接非常有用 - 谢谢您。 - davidA
3个回答

4
我认为你正在寻找这样的东西:
#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 ();

我认为这更接近于您所要求的内容。

在线演示


编辑:根据 @eerorika 的评论,我添加了一个虚析构函数到 Base 中,我太粗心了竟然忘了它!


谢谢。因此,它需要显式的虚拟机制。我认为这要求每个派生类实现自己的 clone 版本,这是可以管理的,但不幸的。然而,我确实赞赏这将在比我在其他地方找到并发布为另一个答案的模板技术中适用于更多情况。 - davidA
1
请注意,此示例程序的行为未定义(如果首次编译,则 OP 的尝试也是如此)。要修复它,请将 Base 的析构函数设置为虚函数。 - eerorika
我认为这要求每个派生类实现自己的clone版本。虽然Base总是可以实现一个默认值。 - Paul Sanders

4

使用奇异递归模板模式可以缓解Paul's solution中的不幸重复:

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; }
};

很好(填充词 填充词) - Paul Sanders

0

我在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,因为调用者知道具体类型。但是,如果调用者只有对 Base 的引用或指针,我不确定这仍然起作用。


3
我不确定如果调用者只有对基类的引用或指针是否仍然有效。我不认为它会有效。 - Paul Sanders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接