草庐IT

c++ - 工厂函数的最佳智能指针返回类型是什么?

coder 2023-05-02 原文

关于智能指针和新的 C++11/14 功能,我想知道对于具有这些功能的类,最佳实践返回值和函数参数类型是什么:

  1. 一个工厂函数(在类之外),它创建对象并将它们返回给类的用户。 (例如打开一个文档并返回一个可用于访问内容的对象。)

  2. 从工厂函数接受对象的实用函数,使用它们,但不取得所有权。 (例如计算文档中单词数的函数。)

  3. 在对象返回后保留对对象的引用的函数(例如,获取对象拷贝以便根据需要在屏幕上绘制内容的 UI 组件。)

工厂函数的最佳返回类型是什么?

  • 如果它是一个原始指针,用户将不得不指向 delete它是正确的,这是有问题的。
  • 如果返回 unique_ptr<>那么用户就无法共享它。
  • 如果是 shared_ptr<>那我要不要绕过shared_ptr<>到处都是类型?这就是我现在正在做的事情,它会导致问题,因为我得到循环引用,防止对象被自动销毁。

效用函数的最佳参数类型是什么?

  • 我想通过引用传递将避免不必要地增加智能指针引用计数,但这有什么缺点吗?我想到的主要问题是它阻止我将派生类传递给采用基类类型参数的函数。
  • 有没有什么方法可以让调用者清楚它不会复制对象? (理想情况下,如果函数体尝试复制对象,代码将无法编译。)
  • 有没有办法让它独立于使用的智能指针的类型? (也许采用原始指针?)
  • 是否有可能拥有 const参数以明确该函数不会修改对象,而不会破坏智能指针的兼容性?

保持对对象的引用的函数的最佳参数类型是什么?

  • 我猜 shared_ptr<>是这里唯一的选项,这可能意味着工厂类必须返回 shared_ptr<>也是,对吧?

这里有一些代码可以编译并希望能说明要点。

#include <iostream>
#include <memory>

struct Document {
    std::string content;
};

struct UI {
    std::shared_ptr<Document> doc;

    // This function is not copying the object, but holding a
    // reference to it to make sure it doesn't get destroyed.
    void setDocument(std::shared_ptr<Document> newDoc) {
        this->doc = newDoc;
    }
    void redraw() {
        // do something with this->doc
    }
};

// This function does not need to take a copy of the Document, so it
// should access it as efficiently as possible.  At the moment it
// creates a whole new shared_ptr object which I feel is inefficient,
// but passing by reference does not work.
// It should also take a const parameter as it isn't modifying the
// object.
int charCount(std::shared_ptr<Document> doc)
{
    // I realise this should be a member function inside Document, but
    // this is for illustrative purposes.
    return doc->content.length();
}

// This function is the same as charCount() but it does modify the
// object.
void appendText(std::shared_ptr<Document> doc)
{
    doc->content.append("hello");
    return;
}

// Create a derived type that the code above does not know about.
struct TextDocument: public Document {};

std::shared_ptr<TextDocument> createTextDocument()
{
    return std::shared_ptr<TextDocument>(new TextDocument());
}

int main(void)
{
    UI display;

    // Use the factory function to create an instance.  As a user of
    // this class I don't want to have to worry about deleting the
    // instance, but I don't really care what type it is, as long as
    // it doesn't stop me from using it the way I need to.
    auto doc = createTextDocument();

    // Share the instance with the UI, which takes a copy of it for
    // later use.
    display.setDocument(doc);

    // Use a free function which modifies the object.
    appendText(doc);

    // Use a free function which doesn't modify the object.
    std::cout << "Your document has " << charCount(doc)
        << " characters.\n";

    return 0;
}

最佳答案

我会以相反的顺序回答,所以从简单的案例开始。

Utility functions that accept objects from the factory functions, use them, but do not take ownership. (For example a function that counts the number of words in the document.)

如果你调用一个工厂函数,你总是通过工厂函数的定义来获得所创建对象的所有权。我认为您的意思是某些 other 客户端首先从工厂获取一个对象,然后希望将其传递给本身不拥有所有权的实用程序函数。

在这种情况下,效用函数根本不应该关心它所操作的对象的所有权是如何管理的。它应该简单地接受一个(可能是 const )引用,或者——如果“无对象”是一个有效条件——一个非拥有的原始指针。这将最大限度地减少接口(interface)之间的耦合,并使实用程序功能最灵活。

Functions that keep a reference to the object after they return (like a UI component that takes a copy of the object so it can draw the content on the screen as needed.)

这些应该是 std::shared_ptr 按值(value)。这从函数的签名中清楚地表明,它们共享参数的所有权。

有时,拥有一个对其参数具有唯一所有权的函数也很有意义(想到的是构造函数)。那些应该采取 std::unique_ptr 按值(或按右值引用),这也将使签名中的语义清晰。

A factory function (outside of the class) that creates objects and returns them to users of the class. (For example opening a document and returning an object that can be used to access the content.)

这是一个困难的,因为两者都有很好的论据,std::unique_ptrstd::shared_ptr .唯一清楚的是返回一个拥有的原始指针是不好的。

返回 std::unique_ptr是轻量级的(与返回原始指针相比没有开销)并且传达了工厂函数的正确语义。调用该函数的人将获得对制造对象的独占所有权。如果需要,客户可以构造一个std::shared_ptrstd::unique_ptr以动态内存分配为代价。

另一方面,如果客户需要 std::shared_ptr无论如何,让工厂使用 std::make_shared 会更有效。以避免额外的动态内存分配。此外,在某些情况下,您只需使用 std::shared_ptr例如,如果托管对象的析构函数是非 virtual并且智能指针将被转换为指向基类的智能指针。但是一个std::shared_ptr开销大于 std::unique_ptr因此,如果后者足够,我们宁愿尽可能避免。

因此,总而言之,我会提出以下准则:

  • 如果您需要自定义删除器,请返回 std::shared_ptr .
  • 否则,如果您认为您的大多数客户都需要 std::shared_ptr无论如何,利用 std::make_shared 的优化潜力.
  • 否则,返回 std::unique_ptr .

当然,您可以通过提供 两个 工厂函数来避免这个问题,其中一个返回 std::unique_ptr一个返回 std::shared_ptr所以每个客户都可以使用最适合其需求的东西。如果你经常需要这个,我想你可以通过一些巧妙的模板元编程来抽象大部分冗余。

关于c++ - 工厂函数的最佳智能指针返回类型是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28294620/

有关c++ - 工厂函数的最佳智能指针返回类型是什么?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - Railstutorial : db:populate vs. 工厂女孩 - 2

    在railstutorial中,作者为什么选择使用这个(代码list10.25):http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-usersnamespace:dbdodesc"Filldatabasewithsampledata"task:populate=>:environmentdoRake::Task['db:reset'].invokeUser.create!(:name=>"ExampleUser",:email=>"example@railstutorial.org",:passwo

  5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  8. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  9. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  10. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

随机推荐