【问题标题】:Elegant solution to duplicate, const and non-const, getters? [duplicate]复制、const 和 non-const、getter 的优雅解决方案? [复制]
【发布时间】:2010-10-25 18:52:52
【问题描述】:

当你拥有它时你不讨厌它

class Foobar {
public:
    Something& getSomething(int index) {
        // big, non-trivial chunk of code...
        return something;
    }

    const Something& getSomething(int index) const {
        // big, non-trivial chunk of code...
        return something;
    }
}

我们不能用另一种方法实现这两种方法,因为您不能从const 版本调用非const 版本(编译器错误)。 需要演员才能从非const 调用const 版本。

有没有真正优雅的解决方案,如果没有,最接近的解决方案是什么?

【问题讨论】:

  • 我认为你需要指出大块代码可能是什么,至少要说明为什么它不能放在单独的函数中。
  • 模板库显式地为某些类型创建一个“constType”。这避免了所有提到的其他方法的问题(需要一点纪律)。
  • 重新问这个问题是非常合理的。建议的副本来自 2008 年,因此我们现在有 2 个新标准 (c++11|14),它们可能提供比建议副本中提供的更优雅的解决方案。特别是因为建议的 const_cast 是一个糟糕的解决方案!请注意,此问题要求 elegant 解决方案,建议的副本中未要求该解决方案,也未在此处给出。

标签: c++ constants


【解决方案1】:

我记得在一本 Effective C++ 书籍中,这样做的方法是通过从其他函数中丢弃 const 来实现非常量版本。

它不是特别漂亮,但很安全。由于调用它的成员函数是非常量的,因此对象本身也是非常量的,并且允许丢弃 const。

class Foo
{
public:
    const int& get() const
    {
        //non-trivial work
        return foo;
    }

    int& get()
    {
        return const_cast<int&>(const_cast<const Foo*>(this)->get());
    }
};

【讨论】:

  • 只要 foo 不是(并且永远不会成为) const 成员,它是安全的。如果是这样,那么您应该重新实现 get 的非 const 版本(或者回到绘图板上,毕竟不要使 foo const ),但是您实际上所做的是没有注意到问题,因为它全部编译好的。
  • “一个常量成员”。或者调用其他函数的 const 返回。基本上,编译器认为它作为 const 返回,但“真的”由于强制转换,它作为非常量返回。因此,在“this”是非常量的情况下,它必须是非常量,编译器无法帮助您强制执行。
  • 对,但是如果你c'n'p,编译器会告诉你代码是否有效。如果你投,你必须自己解决。我并不是说这是不可能的,或者这一定是个坏主意,但 C++ 程序员确实倾向于依赖编译器,尤其是在 const 正确性和其他形式的类型安全方面。这就是它的用途。
  • static_cast&lt;const Foo*&gt;(this) 应该是const_cast&lt;const Foo*&gt;(this)
  • 来自答案文本:由于调用它的成员函数是非常量的,所以对象本身也是非常量的,并且允许丢弃常量。 看完这篇说了很多遍,我终于明白为什么这种做法是安全正确的了。
【解决方案2】:

'const' 的概念是有原因的。对我来说,它建立了一个非常重要的合同,基于该合同编写程序的进一步指令。但是您可以在以下几行中做一些事情:-

  1. 让您的成员“可变”
  2. 将“getters”设为常量
  3. 返回非常量引用

有了这个,如果您需要在使用 getter 的地方维护 const 功能以及非常量用法(危险),可以在 LHS 上使用 const 引用。但是现在程序员有责任维护类不变量。

正如之前在 SO 中所说,丢弃最初定义的 const 对象的 const 并使用它是 U.B.所以我不会使用演员表。同样将非 const 对象设为 const,然后再次丢弃 const 看起来不太好。

我在一些团队中看到的另一个编码准​​则是:-

  • 如果一个成员变量需要在类外修改,总是 通过非常量成员函数返回指向它的指针。
  • 没有成员函数可以返回非常量引用。只有常量 允许从 const 成员函数中引用。

这使得整个代码库有一定的一致性,调用者可以清楚地看到哪些调用可以修改成员变量。

【讨论】:

    【解决方案3】:

    我敢建议使用预处理器:

    #define ConstFunc(type_and_name, params, body) \
        const type_and_name params const body \
        type_and_name params body
    
    class Something
    {
    };
    
    class Foobar {
    private:
        Something something;
    
    public:
        #define getSomethingParams \
        ( \
            int index \
        )
    
        #define getSomethingBody \
        { \
            return something; \
        }
    
        ConstFunc(Something & getSomething, getSomethingParams, getSomethingBody)
    };
    

    【讨论】:

    • 对我来说似乎很困惑。
    • Stroustrup 反对宏。
    【解决方案4】:

    怎么样:

    template<typename IN, typename OUT>
    OUT BigChunk(IN self, int index) {
        // big, non-trivial chunk of code...
        return something;
    }
    
    struct FooBar {
        Something &getSomething(int index) {
            return BigChunk<FooBar*, Something&>(this,index);
        }
    
        const Something &getSomething(int index) const {
            return BigChunk<const FooBar*, const Something&>(this,index);
        }
    };
    

    显然,您仍然会有目标代码重复,但没有源代码重复。与 const_cast 方法不同,编译器会检查你的方法的两个版本的 const 正确性。

    您可能需要将 BigChunk 的两个有趣实例声明为类的朋友。这是对朋友的一个很好的使用,因为朋友功能隐藏在靠近朋友的地方,所以不存在无约束耦合的风险(哦,呃!)。但我现在不会尝试这样做的语法。随意添加。

    BigChunk 可能需要尊重自我,在这种情况下,上述定义顺序不会很好地工作,需要一些前向声明来解决它。

    此外,为了避免在标头中发现 BigChunk 并决定实例化并调用它,即使它在道德上是私有的,您可以将整个文件移到 FooBar 的 cpp 文件中。在匿名命名空间中。带内部联动。还有一个牌子上写着“小心豹子”。

    【讨论】:

    • 你能从声明为const的函数中调用BigChunk吗?我不认为你可以,因为BigChunk 没有声明为const。这是 C++ 模板的主要限制之一。
    • @HelloGoodbye:BigChunk 不是成员函数。在getSomethingconst 重载中,我用INBigChunk 实例化为const FooBar*,所以当然可以将this 作为第一个参数传递。
    • 啊,我没有注意到它不是!嗯,那这个方案其实挺好的。
    • 我想唯一的问题是你必须将你想在函数中使用的所有私有成员变量作为参数发送给函数,除非你有这些变量的 getter/setter 方法。然后,您必须复制参数类型可能是一个问题。您甚至可能必须为模板提供这些参数的类型,因为它们可能必须在 const/non-const 版本中给出。如果您不小心使用了不正确的类型,您可能最终会隐式转换为您不想使用的另一种数据类型,而不会注意到它。
    • 另一方面,您可以将函数声明为 const 成员函数,仍然为其提供 this 指针,并且您将自动能够通过 this 访问所有私有成员指针。但是,该函数将成为另一个符号,在包含头文件的所有翻译单元中都可见,并且还必须由链接器处理,这反过来会稍微增加链接时间。我想总会有一些收获,对吧? :)
    【解决方案5】:

    为什么不直接将公共代码提取到一个单独的私有函数中,然后让其他两个调用它?

    【讨论】:

    • 问题是如果私有函数是非常量的,那么const版本就不能调用它。反之,如果私有函数为 const,则无法返回Something&amp;(假设返回值是Foo 的成员)。所以Something&amp; getSomething(int index)const Something&amp; getSomething(int index) const不可能同时调用同一个私有方法。
    【解决方案6】:

    const 对该对象的引用是有意义的(您限制了对该对象的只读访问),但如果您需要允许非const 引用,您不妨将会员公众。

    我相信这是一个 la Scott Meyers(高效 C++)。

    【讨论】:

    • 公众会员?不是很OO-ish...
    • 我可能会收回这一点,因为我没有阅读关于拥有“非平凡代码”的部分,使这些函数不能直接获取。
    • 实际上,当您想到它时。如果不被滥用,公共成员是这个问题的最简单和最直接的答案。如果有人可以通过使用适当的 getter/setter 来操纵它们,那么实际上它就是一个公共成员。也许将其公开是更好的代码,因为它告诉客户,“我是公共成员,你想对我做任何事情”。这比实现两种类型的 getter 要简单得多。
    • “您不妨将成员设为公开”。不,因为在 getter 中有一个“大而重要的代码块”。这个吸气剂不仅仅是暴露一个成员,它还在做重要的工作。然后将结果公开为非常量是否是一个好主意当然是另一个问题......
    【解决方案7】:

    尝试通过重构代码来消除 getter。如果只有极少数其他事物需要Something,请使用友元函数或类。

    一般来说,Getter 和 Setter 会破坏封装,因为数据是公开的。使用朋友只向少数人公开数据,因此可以提供更好的封装。

    当然,这并不总是可行的,因此您可能会被吸气剂卡住。至少,大部分或所有“重要的代码块”应该在一个或多个私有函数中,由两个 getter 调用。

    【讨论】:

    • 什么?! getter 和 setter 打破封装?而朋友不呢?如果类内部结构(私有部分)发生变化,变量被重命名或消除怎么办?你重写所有的朋友吗? -1 来自我...
    • 是的,朋友真的太没用了。许多 C++ 开发人员认为它们破坏了封装,而实际上它们(根据 Herb Sutter 的话)实际上增加了封装。
    • Paulius:假设您有 2 个类使用彼此的数据。允许访问该数据的最佳方式是什么?公共获取者和设置者还是让他们成为朋友?使用公共 getter 和 setter,任何代码都可以使用这些 getter 和 setter。有了朋友,只有朋友可以访问数据。谁可以查看和/或修改数据的范围大大减少,所以是的,朋友可以提供比 getter 和 setter 更好的封装。我真的不知道为什么当提到朋友时人们会恐慌。朋友很好。
    • 当班级实际上是好伙伴时,朋友是好朋友。如果班级之间几乎不认识,或者另一个班级想要访问数据,那么朋友是很糟糕的,所以你只需添加另一个朋友。 C++ != Facebook,类不应该响应好友请求。我会说如果这些课程不是一起设计的,请不要使用朋友。一旦解决了,是的,朋友很好:-)
    • 我当然同意并且应该在我的回答中明确指出,与友谊相关的课程必须非常相关。不应该为了方便而使用它(getter 和 setter 也不应该)。
    【解决方案8】:

    我会将 const 转换为非常量(第二个选项)。

    【讨论】:

    • 它会工作,但我不认为这很优雅......
    • @Benoît:不,不优雅,但务实。
    • +1:我认为这是 const_cast 为数不多的有效用途之一 - 它绝对胜过代码重复和/或跳过各种麻烦。
    • 我并不反对 :)
    • 如果 something 被声明为 const,那么您将遇到一个您不会注意到的问题,因为您的程序仍在编译。
    猜你喜欢
    • 1970-01-01
    • 2020-04-07
    • 2011-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-29
    相关资源
    最近更新 更多