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

How to create a unique_ptr to a value passed by base class reference?

提问人:davidA 提问时间:3/26/2023 最后编辑:davidA 更新时间:3/26/2023 访问量:148

问:

我有和类,我需要从中多态行为,通过虚拟成员函数:BaseDerivedfoo()

#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);
      |            ~~~~~~~~~~~~~~~~^~~~~~

除此之外,我希望能够在程序的大部分内容中处理具有值语义的实例,但是有一种情况是我需要将实例添加到集合 () 并保留多态性,因此在这种情况下,我需要能够将 s 添加到对象的新副本中。因此,我有一个函数,它接受对一个对象的引用,并打算制作一个副本,然后由 一个 拥有,然后返回这个副本。clone()DerivedDerivedstd::vector<std::unique_ptr<Base>>unique_ptrDerivedclone()Derivedunique_ptr

不幸的是,我无法弄清楚如何制作对抽象类型的引用的多态副本,例如 .Base

我不能按值传递参数,然后将隐式副本移动到 中,因为不可能有 的实例,我需要多态性。unique_ptrBase

我考虑使用转发引用,例如:

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

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

但我不想在调用者那里公开所有权并要求他们调用 - 它应该可以自由地传递对现有对象的引用并期望复制它,而不是获取它的所有权。我实际上希望它被复制,并且源对象保持完整并能够再次使用(例如制作更多副本)。std::moveclone

注意:如果我能让它工作,那么采取一个 可能会更好,因为毕竟我是出于性能原因而通过引用。cloneconst Base &

我在这里尝试做的事情 - 制作一个副本,作为 管理,而不对调用者施加移动语义 - 甚至可能吗?unique_ptr

C++ 复制 按引用传递 C++20 唯一 PTR

评论

1赞 463035818_is_not_an_ai 3/26/2023
create对于制作副本的函数来说,这不是一个好名称。 或者会更好copyclone
0赞 davidA 3/26/2023
是的,这是一个公平的观点,我 100% 同意。我最初让它创建一个并返回它,但我把它剥离出来以使其更简单,并且无意中留下了名为 .编辑:我已在我的问题中将“创建”重命名为“克隆”。std::vector<unique_ptr<Base>>create
1赞 JaMiT 3/26/2023
发现了一些相关的问题,但他们假设这是一个(虚拟)成员函数:派生的 C++ 类如何通过基指针克隆自身? -- 是否可以克隆多态对象,而无需手动将重写的克隆方法添加到 C++ 中的每个派生类中? -- 从基类指针克隆派生类clone()
0赞 davidA 3/26/2023
@JaMiT这些链接非常有用 - 谢谢。

答:

4赞 Paul Sanders 3/26/2023 #1

我想你正在寻找这样的东西:

#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

评论

0赞 davidA 3/26/2023
谢谢。因此,它需要显式的虚拟机。我认为这要求每个派生类实现自己的版本,这是可管理的,但很不幸。但是,我确实很欣赏,这将比我在其他地方找到并作为另一个答案发布的模板化技术在更多情况下起作用。clone
1赞 eerorika 3/26/2023
请注意,此示例程序的行为是未定义的(如果首先编译,OP 的尝试也是如此)。要修复它,请将析构函数设为 virtual。Base
0赞 Paul Sanders 3/26/2023
我认为这要求每个派生类实现自己的克隆版本事实上,虽然总是可以实现默认值。Base
0赞 davidA 3/26/2023 #2

我在 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

这允许在提供的参数上调用 copy-constructor,就好像它是 一样,因为调用者知道具体类型。我不确定如果调用者只有一个引用或指针,这是否仍然有效。make_uniqueDerivedBase

评论

3赞 Paul Sanders 3/26/2023
不过,如果调用者只有指向 Base 的引用或指针,我不确定这是否仍然有效。不明白它怎么可能,不。
4赞 eerorika 3/26/2023 #3

Paul 解决方案中不幸的潜在重复可以使用好奇的重复模板模式来缓解:

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

评论

0赞 Paul Sanders 3/27/2023
尼斯(填充物填充物)