【问题标题】:What's the best way to deal with a non-const-aware Library/SDK?处理非 const 感知库/SDK 的最佳方法是什么?
【发布时间】:2018-07-28 03:50:36
【问题描述】:

我现在使用 3ds max SDK 已经工作了很长时间,它几乎在所有部分都没有使用 const。所以即使是BitmapWidth()Height() getter 也不会被标记为const。这在小项目中已经是一个真正的烦恼,但由于我一直在做一个更大的项目,它变得越来越可怕。

例如,出于性能原因,我在多个类实例中将单个 Bitmap 实例作为 shared_ptr<Bitmap> 成员。当然,在某些情况下,我想尽一切办法避免单个实例可能会更改所有实例的属性,因此所有原始指针获取器(SDK 必需)都提供const Bitmap*。不幸的是,现在我什至无法询问 const Bitmap* 的宽度 - 因为 Width() 是非常量的。

我在问自己处理这个问题的最佳方法是什么。我看到三个选项:

  • 完全忘记 const,让一切都变成非 const。我曾经在较小的项目中这样做,但就像我说的那样,使用更复杂的技术会变得更加危险。
  • 在每一个需要的地方创建一个就地const_cast。很多地方都会出现这种情况,而且读起来很糟糕。
  • 为 3ds max 类编写包装器,它们至少为可能高度安全的方法提供const 方法。这会将所有const_cast 封装在一个地方,也适用于其他项目。

我已被警告(而且我知道)这可能是基于意见的。但是这个烦人的问题我已经处理了很长时间了,我很想找到解决办法,所以需要别人的经验。

【问题讨论】:

  • 包装API 时要考虑的一个问题是增加了维护负担。如果他们的 API 更改,您必须在包装器中反映这些更改。
  • 我不会想太多而选择 1; const 正确性有点像 git 中的干净历史(变基而不是合并):理论上很好,人们在其中投入了大量时间,但最终在它的有用性上被高估了。许多其他主流语言没有它,但它们做得很好。
  • @Galik 我非常不同意你的说法,API 包装器倾向于减少维护负担,因为这种方法能够将 API 更改引起的修改封装在包装器本身内部,通常无需使用包装。这通常可以通过仅更改包装器而不是更改整个代码库来逃脱。
  • @user2328447 • 你已经发现—— 痛苦地—— const 正确性的病毒性质。我会将 const 正确的代理包装器编写为无 const 库/SDK 的防火墙。
  • 出于这个原因,我有效地分叉了一个库。从这些方面来说这很可怕,但在其他方面正是我所需要的。

标签: c++ constants 3dsmax


【解决方案1】:

首先,我想提一下,缺乏const的正确性可能是由实现细节所证明的,例如getter函数可能对内部同步原语执行锁定,因此总是改变内部状态并且不能标记为@ 987654323@:

int Bitmap::Width(void)
{
     int width{};
     ::std::lock_guard<::std::mutex> const lock{m_sync};
     width = m_width;
     return width;
}

作为一种解决方法,您可以编写一个专用的PImpl 位图包装器,使用适当的const 限定符限制对感兴趣的位图实现转发功能的直接访问:

class SharedBitmap
{
    private: ::std::shared_ptr<Bitmap> m_p_bitmap;

    public: int Width(void) const
    {
        return m_p_bitmap->Width();
    }

    // other methods...
};

请注意,此方法与所讨论的第三个选项不同,因为它不涉及const_cast

【讨论】:

  • 这是个好主意 - 我必须考虑一下。但是关于互斥锁:我通常特别声明互斥锁mutable,因为在我看来,它们并不是该类的真正部分/成员,而是锁定“真实”成员以防止线程冲突的特殊项目。因此,必须锁定的 getter 也可以是 const。
  • @James 以及如何在这里应用 CRTP?另请注意,OP 已经在使用shared_ptr&lt;Bitmap&gt;,因此将其包装到 PImpl 类中似乎是合乎逻辑的下一步。
  • @user2328447 我认为值得绝对确定非常量函数真的可以是const,然后再这样对待它们。例如,可能在调用堆栈深处的某个地方修改了一些未同步的全局。
  • “例如 getter 函数可能对内部同步原语执行锁定,因此总是改变内部状态并且不能标记为 const:” 这是废话 - 这就是 @987654331 @ 是为了。
  • @juanchopanza 是真的。不幸的是,我没有完整的源代码,但至少很多 getter 应该是安全的,因为我可以在他们读取成员的标题中追踪。
【解决方案2】:

根据我的经验(10 年),“const”带来的麻烦多于它的帮助。更不用说代码变得更长,更难阅读。如果你想知道一个库是如何工作的,你无论如何都要阅读手册,而不是标题。如果你想知道你做对了,你可以运行功能测试。该死的,甚至还有静态分析工具检查变量是否被写入,而不会用无用的非功能性元数据给代码带来负担,以捕获未记录的使用模式。并且由于有很多方法可以打破 const,它是捕获此类错误的正确方法。

总之,根据我的经验,选项 1 是最有效的解决方案。 (这是意见吗?不同意的人可能会这么认为。)

对于 const 的快速清除后,您可以使用 #define const 甚至 -Dconst 将其删除,但 是否安全可能取决于您的具体情况,一个 illegal use 是为标准标题执行此操作。在进行白盒测试时,我已经完成了类似#define private|protected public 之类的黑客攻击,而不是与friend 搞混,就像一个魅力!

知道“常量变量”的概念在许多编程语言中是无效的,没有它它们似乎也能正常工作。

唯一需要 const 的情况是 C 字符串常量/字符串文字。不过,这似乎不是你的情况。

【讨论】:

  • const 为您提供了一个关于函数线程安全性的非常好的指标
  • @James 没有必要超越任何东西。 const 限定符的存在与函数的线程安全性之间根本没有联系。基于 const 限定符的存在对内部状态的变化做出“原子或互斥保护”的任何假设是完全错误的。
  • @Andreas:我认为您不了解 UB 是什么。标准说,如果你这样做,你的程序的行为是不确定的。时期。故事结局。在告诉人们“别废话”之前执行basic research
  • 哇,说到废话,我直到现在才看到你的答案。到处都是可怕的建议。当然,您有权发表自己的意见;但是,您的意见是错误的。 :)
  • @Andreas:不,这在您自己的代码中是非法的(除非您不使用标准标头,但我很乐意看到)。用人身攻击来消除它并倾听/学习。
猜你喜欢
  • 1970-01-01
  • 2010-09-06
  • 2012-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-30
  • 2012-09-18
  • 2011-05-20
相关资源
最近更新 更多