当前位置:  开发笔记 > 编程语言 > 正文

如何删除类似const和非const成员函数之间的代码重复?

如何解决《如何删除类似const和非const成员函数之间的代码重复?》经验,为你挑选了7个好方法。

假设class X我想要返回内部成员的访问权限:

class Z
{
    // details
};

class X
{
    std::vector vecZ;

public:
    Z& Z(size_t index)
    {
        // massive amounts of code for validating index

        Z& ret = vecZ[index];

        // even more code for determining that the Z instance
        // at index is *exactly* the right sort of Z (a process
        // which involves calculating leap years in which
        // religious holidays fall on Tuesdays for
        // the next thousand years or so)

        return ret;
    }
    const Z& Z(size_t index) const
    {
        // identical to non-const X::Z(), except printed in
        // a lighter shade of gray since
        // we're running low on toner by this point
    }
};

的两个成员函数X::Z()X::Z() const具有大括号内相同的代码.这是重复的代码,并且可能导致具有复杂逻辑的长函数的维护问题.

有没有办法避免这种代码重复?



1> jwfearn..:

有关详细说明,请参见第28页码"避免复制const和非const成员函数".参见图23,在第3项" const尽可能使用",有效C++,3D编辑,Scott Meyers,ISBN-13:9780321334879.

替代文字

这是迈耶斯的解决方案(简化):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast(static_cast(*this).get());
  }
  char c;
};

两个强制转换和函数调用可能很丑,但它是正确的.迈耶斯有一个彻底的解释原因.


没有人因为跟随Scott Meyers而被解雇:-)
一般情况下,我建议使用const_cast而不是static_cast来添加const,因为它会阻止您意外更改类型.
witkamp是正确的,一般来说使用const_cast是不好的.正如迈耶斯所解释的那样,这是一个特殊情况.@Adam:ROM => const很好.const == ROM显然是无稽之谈,因为任何人都可以将非const转换为const willy-nilly:它等同于选择不修改某些东西.
@HelloGoodbye:我认为Meyers假设来自类界面设计师的*modicum*智能.如果`get()const`返回被定义为const对象的东西,那么根本不应该有`const()`的非const版本.实际上我对此的想法随着时间的推移而发生了变化:模板解决方案是避免重复的唯一方法*和*得到编译器检查的const正确性,所以我个人不再使用`const_cast`以避免重复代码,我选择将欺骗代码放入功能模板中还是将其留下来.
以下两个模板极大地帮助了这个解决方案的可读性:`template const T&constant(T&_){return const_cast (_); }`和`template T&variable(const T&_){return const_cast (_); }`.然后你可以这样做:`return variable(constant(*this).get());`
哎哟...如果你指望const == ROM,你可能已经遇到了麻烦.编译器不足以证明这样一个强有力的断言.
如果你的`const char&get()const`返回一些实际声明为const的东西怎么办?然后你的非常量版本的`get`将有效地const_cast一些从未被认为是可以访问的东西.
@CaseyRodarmor [现在使用C ++ 17`std :: as_const()更好了](https://en.cppreference.com/w/cpp/utility/as_const)。

2> Kevin..:

是的,可以避免代码重复.您需要使用const成员函数来获取逻辑并让非const成员函数调用const成员函数并将返回值重新转换为非const引用(或者如果函数返回指针则返回指针):

class X
{
   std::vector vecZ;

public:
   const Z& Z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& Z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast( static_cast(*this).Z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast( constMe.Z(index) );
   }
 #endif
};

注意:重要的是不要将逻辑放在非const函数中并让const函数调用非const函数 - 它可能导致未定义的行为.原因是常量类实例被转换为非常量实例.非const成员函数可能会意外地修改类,C++标准声明这将导致未定义的行为.


虽然我理解解决方案可能很难看,但想象一下确定返回的代码是50行.然后重复是非常不受欢迎的 - 特别是当您必须重新考虑代码时.在我的职业生涯中,我曾多次遇到这种情况.
嘿,不要这个!它可能很难看,但据Scott Meyers说,它(几乎)是正确的方法.请参阅标题"避免const和非成本成员函数中的重复"标题下的_Effective C++ _,3d ed,Item 3.
这与Meyers的区别在于Meyers有static_cast (*this).const_cast用于删除const,而不是添加它.
@VioletGiraffe我们知道该对象最初并不是const,因为它是非const对象的非const成员,我们知道这是因为我们处于所述对象的非const方法中.编译器不进行此推断,它遵循保守规则.为什么你认为const_cast存在,如果不是因为这种情况?
哇......太可怕了.你只是增加了代码量,降低了清晰度,并添加了*two*stinkin'const_cast <> s.也许你有一个例子,这实际上有意义吗?
凯文 - 你需要至少一个编辑#6 - 在丑陋的演员阵容中你有"const_cast ",它应该是"const_cast "

3> Pait..:

我认为Scott Meyers的解决方案可以通过使用tempate helper函数在C++ 11中得到改进.这使得意图更加明显,可以重复用于许多其他的getter.

template 
struct NonConst {typedef T type;};
template 
struct NonConst {typedef T type;}; //by value
template 
struct NonConst {typedef T& type;}; //by reference
template 
struct NonConst {typedef T* type;}; //by pointer
template 
struct NonConst {typedef T&& type;}; //by rvalue-reference

template
typename NonConst::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast::type>(
         (obj->*memFun)(std::forward(args)...));
}

可以通过以下方式使用此辅助函数.

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

第一个参数始终是this-pointer.第二个是指向要调用的成员函数的指针.之后,可以传递任意数量的附加参数,以便将它们转发给函数.由于可变参数模板,这需要C++ 11.


如果`getElement`被重载,这个解决方案不能正常工作.然后,如果不明确地给出模板参数,则无法解析函数指针.为什么?
遗憾的是我们没有`std :: remove_bottom_const`与`std :: remove_const`一起使用.
@ v.oddou:`std :: remove_const `是`int const&`(删除顶级`const`资格),因此在这个答案中体现了'NonConst `.假定的`std :: remove_bottom_const`可以删除底层的`const`限定,并且正好执行`NonConst `在这里做的事:`std :: remove_bottom_const :: type` =>`int&`.

4> David Stone..:

C++ 17已经更新了这个问题的最佳答案:

T const & f() const {
    return something_complicated();
}
T & f() {
    return const_cast(std::as_const(*this).f());
}

这有以下优点:

显而易见的是什么

具有最小的代码开销 - 它适合单行

难以出错(只能volatile意外抛弃,但是volatile难得一见)

如果你想要完整的扣除路线,那么可以通过辅助功能来完成

template
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast(value);
}
template
constexpr T * as_mutable(T const * value) noexcept {
    return const_cast(value);
}
template
constexpr T * as_mutable(T * value) noexcept {
    return value;
}
template
void as_mutable(T const &&) = delete;

现在你甚至不能搞砸了volatile,用法看起来像

decltype(auto) f() const {
    return something_complicated();
}
decltype(auto) f() {
    return as_mutable(std::as_const(*this).f());
}



5> Steve Jessop..:

比迈耶斯更冗长,但我可能会这样做:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

私有方法具有不受欢迎的属性,它返回一个非const Z&for const实例,这就是它为私有的原因.私有方法可能会破坏外部接口的不变量(在这种情况下,所需的不变量是"const对象不能通过它获得的引用修改为它具有的对象").

请注意,注释是模式的一部分 - _getZ的接口指定调用它永远无效(显然除了访问器):无论如何这样做都没有可能的好处,因为它输入了1个字符,而不是导致更小或更快的代码.调用该方法相当于使用const_cast调用其中一个访问器,您也不希望这样做.如果你担心明显错误(这是一个公平的目标),那么称之为const_cast_getZ而不是_getZ.

顺便说一句,我很欣赏迈耶斯的解决方案.我对它没有任何哲学上的反对意见.但就个人而言,我更喜欢一点点的受控重复,以及一种只能在某些严格控制的情况下调用的私有方法,而不是一种看起来像线路噪声的方法.选择你的毒药并坚持下去.

[编辑:Kevin正确地指出_getZ可能想要调用另一个方法(比如generateZ),这个方法与getZ一样是const专用的.在这种情况下,_getZ会看到一个const Z&并且必须在返回之前进行const_cast.这仍然是安全的,因为样板访问器可以控制所有内容,但是它的安全性并不明显.此外,如果你这样做,然后将generateZ更改为始终返回const,那么你还需要将getZ更改为始终返回const,但编译器不会告诉您这样做.

关于编译器的后一点也适用于Meyers的推荐模式,但关于非显而易见的const_cast的第一点不是.所以总的来说,我认为如果_getZ结果需要一个const_cast作为其返回值,那么这种模式会失去很多超过Meyers的价值.由于与Meyers相比它也有缺点,我想我会在那种情况下转向他.从一个重构到另一个很容易 - 它不会影响类中的任何其他有效代码,因为只有无效代码和样板文件调用_getZ.


-1:这在许多情况下不起作用.如果`_getZ()`函数中的`something`是一个实例变量怎么办?编译器(或至少一些编译器)会抱怨,因为`_getZ()`是const,所以在其中引用的任何实例变量也是const.因此``something`将是const(它将是`const Z&`类型)并且无法转换为`Z&`.在我(实际上有点受限)的经验中,大多数时候`something`在这种情况下是一个实例变量.
这仍然存在一个问题,即对于X的常量实例,您返回的内容可能是常量。在这种情况下,您仍然需要_getZ(...)中的const_cast。如果被以后的开发人员滥用,它仍然可以导致UB。如果要返回的东西是“可变的”,那么这是一个很好的解决方案。
@GravityBringer:那么"东西"需要涉及一个`const_cast`.它旨在成为从const对象获取非const返回所需的代码的占位符,而不是作为复制getter中**的占位符.所以"某事"不仅仅是一个实例变量.
我知道了.但这确实削弱了该技术的实用性.我会删除downvote,但不会让我.

6> gd1..:

好问题和好答案.我有另一种解决方案,它不使用强制转换:

class X {

private:

    std::vector v;

    template
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

但是,它具有需要静态成员的丑陋以及在其中使用instance变量的需要.

我没有考虑这个解决方案的所有可能(负面)含义.如果有的话请告诉我.


好吧,让我们假设您添加了更多的样板.如果有的话,这应该用作为什么语言需要一种方法来修改函数限定符以及返回类型`auto get(std :: size_t i) - > auto(const),auto(&&)`的示例.为什么'&&'?啊,所以我可以说:`auto foo() - > auto(const),auto(&&)= delete;`
该解决方案还有一个优势(相对于const_cast而言),可以返回“迭代器”和“ const_iterator”。

7> Andy Balaam..:

您也可以使用模板解决此问题.这个解决方案稍微丑陋(但丑陋隐藏在.cpp文件中)但它确实提供了constness的编译器检查,并且没有代码重复.

.h文件:

#include 

class Z
{
    // details
};

class X
{
    std::vector vecZ;

public:
    const std::vector& GetVector() const { return vecZ; }
    std::vector& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.cpp文件:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

我可以看到的主要缺点是,因为该方法的所有复杂实现都在一个全局函数中,您需要使用上面的GetVector()等公共方法来获取X的成员(其中总是需要一个const和非const版本)或者你可以使这个功能成为朋友.但我不喜欢朋友.

[编辑:删除了在测试期间添加的cstdio不需要的包含.]


您始终可以使复杂的实现功能成为静态成员,以获得对私有成员的访问权限.该函数只需要在类头文件中声明,该定义可以驻留在类实现文件中.毕竟,这是课程实施的一部分.
推荐阅读
爱唱歌的郭少文_
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有