草庐IT

c++ - 用于异常处理的基类技术

coder 2023-11-16 原文

GotW#8的任务是在 C++ 中实现一个异常中立的通用堆栈数据结构,假设只有模板参数的析构函数不抛出。诀窍是处理可能抛出的模板参数操作(构造函数、复制构造函数、赋值),以便在它们抛出时使堆栈保持一致状态。

在解决方案中,Herb Sutter 说

To keep this solution simpler, I've decided not to demonstrate the base class technique for exception-safe resource ownership.

谷歌搜索后,我找到了 this answer由 Dave Abrahams 于 1997 年提出。在他的解决方案中,他处理基类中内存的分配和删除,并在子类中实现堆栈操作。这样,他确保复制构造函数中的元素复制与内存分配分开,这样如果复制失败,无论如何都会调用基类析构函数。

作为引用,这是 Dave 的复制构造函数,其中添加了我的评论:

// v_ refers to the internal array storing the stack elements
Stack(const Stack& rhs)
        : StackBase<T>( rhs.Count() ) // constructor allocates enough space
                                      // destructor calls delete[] appropriately
{
        while ( Count() < rhs.Count() )
           Push( rhs.v_[ Count() ] ); // may throw
}

如果基构造函数成功,即使子类的复制构造函数抛出,也能保证基析构函数中的内存清理。

我的问题是:

  1. 除上文所述外,该方法还有其他好处吗?
  2. 我自己解决这个问题时想出了这个复制构造函数:

    // v_ refers to the internal array storing the stack elements
    // vsize_ is the amount of space allocated in v_
    // vused_ is the amount of space used so far in v_
    Stack (const Stack &rhs) :
            vsize_ (0), vused_ (0), v_ (0) {
        Stack temp (rhs.vused_); // constructor calls `new T[num_elements]`
                                 // destructor calls `delete[] v_`
        std::copy (rhs.v_, rhs.v_ + rhs.vused_, temp.v_); // may throw
        swap (temp);
    }
    void swap (Stack &rhs) {
        std::swap (v_, rhs.v_);
        std::swap (vused_, rhs.vused_);
        std::swap (vsize_, rhs.vsize_);
    }
    

    与这种方法相比,我发现使用基类有点麻烦。有什么理由应该优先使用基类技术而不是这种临时复制然后交换的方法吗?请注意,Dave 和我都已经有了 swap() 成员,因为我们在 operator=() 中使用了它。

  3. Dave Abrahams 的技术似乎不是很出名(根据 Google 的说法)。它有不同的名称吗?这是标准做法吗?我错过了什么吗?

注意事项:

  • 假设循环中 Dave 的 Push() 等同于我对 std::copy 的使用
  • 让我们将智能指针排除在答案之外,因为它们的使用会带走本练习中显式管理内存的重点

最佳答案

从行为上来说,这两个实现是相同的。他们都设置了一个托管内存分配对象,如果构造函数失败,该对象将在作用域退出时进行清理。复制到临时变量可能会更昂贵,但如评论中所述,std::move可能会抵消这些额外费用。在回答您的具体问题时:

  1. Abraham 的示例确实将堆分配与您的实际类实现细节相分离。在您的代码中,如果您在复制数组之前/之后执行更复杂的内存操作,则确保正确管理所有实体可能会稍微困难一些。否则,关于第一个实现的行为,我看不到任何您尚未涵盖的明确细节。
  2. Abraham 的实现确实将清理抽象到一个位置。如果多个类使用 StackBase<T>然后他们每个人都可以安全地假设如果他们抛出异常,他们的动态内存将被清理。在您的实现中,您需要重写(至少部分)临时对象和交换代码以实现相同的目的。实际上,此实现减少了实现 StackBase 的多个子类的行数。但是,如果您希望在其他一些基类之上分配额外的内存,则您的实现会避免多重继承。您的代码还避免了模板代码膨胀编译时间/大小——尽管无论如何我通常不认为这在大多数情况下是一个很大的负面影响。除非我尝试编写非常通用的用例代码,否则我可能会默认使用与您的代码接近的内容。
  3. 我不知道这种方法是否有特定的名称——如果我找到一个,我会更新它——但我之前至少在一本 C++ 编程书籍中看到过它。

关于c++ - 用于异常处理的基类技术,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15644406/

有关c++ - 用于异常处理的基类技术的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

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

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

  4. ruby-on-rails - Rails - 乐观锁定总是触发 StaleObjectError 异常 - 2

    我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd

  5. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  6. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  7. ruby - 在 Ruby 中重新分配常量时抛出异常? - 2

    我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案

  8. ruby - inverse_of 是否适用于 has_many? - 2

    当我使用has_one时,它​​工作得很好,但在has_many上却不行。在这里您可以看到object_id不同,因为它运行了另一个SQL来再次获取它。ruby-1.9.2-p290:001>e=Employee.create(name:'rafael',active:false)ruby-1.9.2-p290:002>b=Badge.create(number:1,employee:e)ruby-1.9.2-p290:003>a=Address.create(street:"123MarketSt",city:"SanDiego",employee:e)ruby-1.9.2-p290

  9. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  10. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

随机推荐