【问题标题】:C++ how to pass unique_ptr to function and still benefit from polymorphism?C++ 如何将 unique_ptr 传递给函数并仍然受益于多态性?
【发布时间】:2017-11-14 00:36:52
【问题描述】:

我有以下类定义:

class A {...}
class B : public class A {...}

和一个所有者类定义:

class C {
    A* a;
public: 
    C (A* a) { this->a = a; }
}

然后当我尝试调用以下内容时,一切都很好:

B* b = new B();
C(b);

但是我有以下所有者类定义:

class C {
    std::unique_ptr<A> a;
public: 
    C(std::unique_ptr<A>& a) { this->a = std::move(a); }
}

当我尝试调用以下命令时,我收到一个编译器错误,提示无法从 B 的 unique_ptr 转换为 A 的 unique_ptr&:

std::unique_ptr<B> b(new B());
C(b);

我确实意识到 std::unique_ptr& 和 std::unique_ptr 是不一样的。但我不确定将唯一指针传递给所属类的标准协议是什么。

我尝试了以下方法:

class C {
    std::unique_ptr<A> a;
public: 
    C(std::unique_ptr<A> a) { this->a = std::move(a); }
}

std::unique_ptr<B> b = std::make_unique<B>();
C(std::move(b));

这可行,但感觉反直觉,因为使用 unique_ptr 而不是 shared_ptr 的部分目的是通过不必复制内存地址来提高性能。但在这种情况下,它不是复制内存地址,而是创建一个全新的 unique_ptr 以仅按值传递它,只是在函数超出范围后将其丢弃。

注意:我使用的是 VS C++11 编译器

【问题讨论】:

  • 给func函数一个unique_ptr:func(a.get());
  • 根据文档 (en.cppreference.com/w/cpp/memory/unique_ptr/make_unique) std::make_unique 是 C++ 14(因此 您已经在使用 C++ 14 功能)。否则,我认为std::unique_ptr 在 C++ 11 和 14 之间没有任何重大变化。您的 C 构造函数可能应该是:C(std::unique_ptr&lt;A&gt;&amp;&amp; a) : a(std::move(a)) {}
  • @sailfish009 类 C 需要获得指针的所有权,所以你的答案是行不通的。
  • @jahithber 如果你通过了 func(a.release()) 那么你的类可以取得对象的所有权。
  • @Abhishek 好的,我明白了。我看到这一点的方式是,当您通过指向函数的指针传递时,不期望所有权将被转移。因此调用者代码可能会在指针上调用 delete 并导致问题。具有唯一指针作为其输入参数的函数明确表示它将对指针的唯一性做一些事情,因此存在转移所有权的期望。所以也许函数可以这样写: public: C(A*& a) { m_a = a; a = nullptr; }

标签: c++11 unique-ptr


【解决方案1】:

不要通过可变引用传递唯一指针。你会有这样的错误。

确实解决办法是传值:

class C {
    std::unique_ptr<A> a;
public: 
    C(std::unique_ptr<A> a) { this->a = std::move(a); }
}

// ...

auto b = std::make_unique<B>();
C(std::move(b));

这可行,但感觉反直觉,因为使用 unique_ptr 而不是 shared_ptr 的部分目的是通过不必复制内存地址来提高性能。

没有。唯一 ptr 的优点是它们是对具有唯一所有权的指针的零开销抽象。

共享指针是有代价的。每次复制它们时,它们都会执行原子引用计数。这会损害缓存,并且不是可以忽略的开销。此外,共享指针隐藏了谁拥有谁。所有者的数量是在运行时确定的,这使得代码更难思考。

顺便说一句,复制地址的开销可以忽略不计。引用大部分时间都是以指针的形式实现的。复制原始指针与通过引用传递的成本相同。由于std::unique_ptr 是零开销抽象,因此复制(或移动)唯一指针的成本与复制原始指针的成本相同,而原始指针的成本又与通过引用传递的成本大致相同。您不必担心复制指针的性能。

但在这种情况下,它不是复制内存地址,而是创建一个全新的 unique_ptr 以仅按值传递它,只是在函数超出范围后将其丢弃。

创建一个全新的unique_ptr 与复制内存地址的成本相同。与其他流行语言不同,C++ 对象默认位于堆栈上,提供与处理原生类型相同的性能。


如果您非常担心性能,请使用优化进行编译,分析您的代码和基准测试。除非你衡量它,否则任何关于性能的陈述都是没有意义的。如果您检查编译器的汇编输出,您会注意到它们是 exactly the same

免责声明:我没有在现场示例中使用 clang 进行编译,因为它甚至在 O2 上也忽略了整个代码。

【讨论】:

  • 感谢您的回答,您对通过 rvalue-reference 进行传递有何感受?这会比价值更有效吗?如果是的话,有什么需要避免的陷阱吗?
  • @jahithber 通过右值引用传递再次与创建新值相同。自己试试吧!我给你链接了一个很棒的网站,你可以看到编译器对你的代码做了什么。但是有一些陷阱:通过引用传递不会转移所有权。您必须使用 move 通过右值引用进行传输,但您的值可能不会实际移动,因为接收右值的函数负责实际移动值或不移动值。我绝不建议通过右值引用传递 unique_ptr。
  • @jahithber 同样,如果您真的想要优化代码,请编写基准测试并对其进行分析。除非您对其进行测量,否则任何其他有关性能的讨论都是推测。我向您展示的网站证明了性能是相同的。如果汇编输出不同并且您担心性能,请分析您的代码
猜你喜欢
  • 1970-01-01
  • 2018-03-12
  • 2012-03-30
  • 2015-09-03
  • 1970-01-01
  • 1970-01-01
  • 2014-11-09
  • 2019-09-27
  • 2013-01-16
相关资源
最近更新 更多