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


242

假设class X我想在以下位置返回对内部成员的访问权限:

class Z
{
    // details
};

class X
{
    std::vector<Z> 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具有大括号内相同的代码。这是重复的代码,可能会对具有复杂逻辑的长功能造成维护问题

有没有办法避免此代码重复?


在此示例中,我将在const情况下返回一个值,因此您无法在下面进行重构。int Z()const {return z; }
马特·普赖斯

1
对于基本类型,您绝对正确!我的第一个例子不是很好。假设我们改为返回一些类实例。(我更新了问题以反映这一点。)
凯文

Answers:


189

有关详细的说明,请参阅第38 页码标题“避免重复const和使用非const成员功能”。23,在有效的C ++中第3项“ const尽可能使用”中斯科特·迈耶斯(Scott Meyers)编辑,3d,ISBN-13:9780321334879。

替代文字

这是Meyers的解决方案(简化):

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

这两个强制类型转换和函数调用可能很难看,但这是正确的。迈耶斯对此做了详尽的解释。


45
没有人追随斯科特·迈耶斯(Scott Meyers):-)
史蒂夫·杰索普

11
witkamp是正确的,通常使用const_cast是不好的。正如Meyers解释的那样,这是特定情况,而实际上并非如此。@Adam:ROM => const很好。const == ROM显然是胡说八道,因为任何人都可以将non-const强制转换为const willy-nilly:等同于选择不修改某些内容。
史蒂夫·杰索普

44
通常,我建议使用const_cast而不是static_cast添加const,因为它可以防止意外更改类型。
格雷格·罗杰斯

6
@HelloGoodbye:我认为Meyers假定类接口设计人员具有一点点智慧。如果get()const返回定义为const对象的内容,则完全不应存在非const版本get()。实际上,我对此的想法已经随着时间而改变:模板解决方案是避免重复获得编译器检查的const-正确性的唯一方法,因此我个人不再使用a const_cast来避免重复代码,我选择在将重复的代码放入功能模板中,否则将其保留。
Steve Jessop

7
以下两个模板大大提高了此解决方案的可读性:template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }template<typename T> T& variable(const T& _) { return const_cast<T&>(_); }。然后您可以执行以下操作:return variable(constant(*this).get());
Casey Rodarmor 2014年

64

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

class X
{
   std::vector<Z> 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<Z&>( static_cast<const X&>(*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<Z&>( constMe.z(index) );
   }
 #endif
};

注意:重要的是不要将逻辑放在非const函数中,而让const函数调用非const函数-这可能会导致未定义的行为。原因是常量类实例被转换为非常量实例。非const成员函数可能会不小心修改该类,而C ++标准状态将导致未定义的行为。


3
哇...好可怕 您只是增加了代码量,降低了清晰度,并添加了两个臭皮的const_cast <>。也许您在脑子里有个例子,这实际上有意义吗?
Shog9年

14
嘿,别这样!这可能很难看,但是根据Scott Meyers的说法,这(几乎)是正确的方法。请参阅“避免在const和非开销成员函数中重复”标题下的有效C ++,3d ed,项目
3。– jwfearn

17
虽然我知道该解决方案可能很丑陋,但可以想象一下确定返回内容的代码长50行。那么复制是非常不希望的-尤其是当您必须重构代码时。我在职业生涯中遇到过很多次。
凯文

8
this与Meyers之间的区别在于Meyers具有static_cast <const X&>(* this)。const_cast用于删除const,而不是添加它。
史蒂夫·杰索普

8
@VioletGiraffe我们知道该对象并不是最初创建的const,因为它是非const对象的非const成员,我们知道这是因为我们处于所述对象的非const方法中。编译器不会进行这种推断,而是遵循一个保守的规则。如果不针对这种情况,您为什么认为const_cast存在?
卡雷斯(Caleth)'17

47

C ++ 17更新了此问题的最佳答案:

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

这具有以下优点:

  • 很明显发生了什么事
  • 最小的代码开销-它适合一行
  • 很难犯错(只能volatile被偶然抛弃,但却volatile是罕见的预选赛)

如果您想走完整的推演路线,则可以通过具有辅助功能来完成

template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
    return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
    return value;
}
template<typename T>
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());
}

请注意,删除了const rvalue重载的“ as_mutable”(通常更可取)会阻止最后一个示例在f()返回T而不是的情况下工作T&
Max Truxa

1
@MaxTruxa:是的,这是一件好事。如果只是编译,我们将有一个悬而未决的参考。在该情况下f()的回报T,我们不希望有两个重载,该const单独的版本就足够了。
大卫·斯通

没错,昨天我为我的全脑放屁道歉,不知道写这篇评论时我在想什么。我看着返回的const / mutable getter对shared_ptr。所以我真正需要的是像as_mutable_ptr它看起来几乎相同as_mutable以上,但它需要和回报shared_ptr和用途std::const_pointer_cast,而不是const_cast
Max Truxa

1
如果一个方法返回,T const*那么它将绑定T const* const&&而不是绑定T const* const&(至少在我的测试中是这样)。我必须添加一个重载T const*作为返回指针的方法的参数类型。
Monkey0506 '19

2
@ monkey0506:我更新了答案以支持指针和引用
David Stone

34

我认为,可以通过使用临时帮助函数在C ++ 11中改进Scott Meyers的解决方案。这使得意图更加明显,并且可以被许多其他吸气剂重用。

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

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

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


3
我们不必std::remove_bottom_const走了,真是太可惜了std::remove_const
TBBle

我不喜欢这种解决方案,因为它仍然嵌入了const_cast。您可以自己制作getElement模板,并使用内部mpl::conditional类型的特性来选择所需的类型,例如iterators或constiterators(如果需要)。真正的问题是当无法对签名的这一部分进行模板化时,如何生成方法的const版本?
v.oddou

2
@ v.oddou:std::remove_const<int const&>int const &(删除顶级const资格),因此NonConst<T>此答案中的体操。推定的条件std::remove_bottom_const可能会删除底层条件const,并精确地NonConst<T>执行以下操作:std::remove_bottom_const<int const&>::type=> int&
TBBle

4
如果getElement过载,此解决方案将无法正常工作。然后,如果不显式提供模板参数,则无法解析函数指针。为什么?
约翰

1
您需要修正使用C ++ 11完美转发的答案:likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }完整:gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83
ShaulF

22

比Meyers更为冗长,但我可以这样做:

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); }
};

private方法具有令人讨厌的属性,即它为const实例返回非const Z&,这就是为什么它是private的原因。私有方法可能会破坏外部接口的不变性(在这种情况下,所需的不变性是“无法通过对const对象的引用来修改其const对象”)。

请注意,注释是模式的一部分-_getZ的接口指定调用它永远无效(很显然,除了访问器之外):这样做没有任何好处,因为它会多输入1个字符,并且不会导致更小或更快速的代码。调用该方法等效于使用const_cast调用其中一个访问器,并且您也不想这样做。如果您担心使错误变得明显(这是一个公平的目标),则将其称为const_cast_getZ而不是_getZ。

顺便说一句,我感谢迈耶斯的解决方案。我对此没有哲学上的异议。但是,就我个人而言,我喜欢一点点受控的重复,并且只喜欢在某些严格受控的情况下才调用私有方法,而不是看起来像线路噪声的方法。选择你的毒药并坚持下去。

[编辑:凯文正确地指出,_getZ可能想要调用另一种方法(例如generateZ),该方法以与getZ相同的方式进行const专业化。在这种情况下,_getZ将看到const Z&,并且必须在返回之前进行const_cast。由于样板访问器可以管理所有操作,因此这仍然是安全的,但并不是很明显它是安全的。此外,如果您这样做,然后在以后将generateZ更改为始终返回const,则还需要将getZ更改为始终返回const,但是编译器不会告诉您这样做。

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


3
这仍然存在一个问题,即对于X的常量实例,您返回的内容可能是常量。在这种情况下,您仍然需要_getZ(...)中的const_cast。如果被以后的开发人员滥用,它仍然可以导致UB。如果要返回的东西是“可变的”,那么这是一个很好的解决方案。
凯文

1
如果以后的开发人员选择忽略其有效使用,头文件以及Doxygen等中的BLOCK CAPITAL指令,则任何私有函数(包括heck,public的公共函数)都可能会被滥用,我无法阻止,而且我不认为这是我的问题,因为这些说明很容易理解。
史蒂夫·杰索普

13
-1:这在许多情况下不起作用。如果something_getZ()函数是一个实例变量?编译器(或至少某些编译器)会抱怨,因为_getZ()是const,所以在其中引用的任何实例变量也都是const。因此something它将是const(它将是type const Z&)并且无法转换为Z&。根据我的经验(在一定程度上是有限的),大多数something情况下都是这样的实例变量。
重力

2
@GravityBringer:然后,“某物”需要包含const_cast。它的目的是为所需的代码来从const对象的非const的回报,而不是作为一个什么占位符是一个占位符一直在重复吸气。因此,“某物”不仅仅是一个实例变量。
史蒂夫·杰索普

2
我懂了。但是,这确实降低了该技术的实用性。我会删除弃权票,但绝对不会让我。
重力

22

好问题,好答案。我有另一个解决方案,不使用强制类型转换:

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    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变量。

我没有考虑此解决方案的所有可能(负面)影响。请让我知道。


4
好吧,让我们简单地了解一下,您添加了更多样板。如果有的话,这应作为该语言为何需要一种方法来修改函数限定符以及返回类型的示例 auto get(std::size_t i) -> auto(const), auto(&&)。为什么是“ &&”?auto foo() -> auto(const), auto(&&) = delete;

gd1:正是我的想法。@kfsone以及我的结论也是如此。
v.oddou

1
@kfsone的语法应包含this关键字。我建议template< typename T > auto myfunction(T this, t args) -> decltype(ident)将this关键字识别为隐式对象实例参数,并让编译器识别myfunction是member或TT会在呼叫站点上自动推导,这将始终是课程的类型,但具有免费的简历资格。
v.oddou

2
该解决方案还有一个优势(与之相比const_cast),可以返回iteratorconst_iterator
Jarod42年

1
如果实现在cpp文件中移动(并且不重复的方法不容易,可能是这种情况),则static可以在文件范围而不是类范围内完成。:-)
Jarod42年

8

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

.h文件:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& 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。]


3
您始终可以使复杂的实现函数成为静态成员,以访问私有成员。该函数只需要在类头文件中声明,该定义可以驻留在类实现文件中。毕竟,它是类实现的一部分。
CB Bailey

啊,是个好主意!我不喜欢模板内容出现在标题中,但是如果从这里开始,它可能使实现变得非常简单,那么它可能是值得的。
Andy Balaam

此解决方案的+1,不会重复任何代码,也不会使用任何丑陋的东西const_cast(这可能会偶然地使实际上本应为const的内容转变为非常量)。
HelloGoodbye

如今,可以通过推导模板的返回类型来简化此操作(由于减少了成员情况下类中必须重复的内容,因此特别有用)。
戴维斯鲱鱼

3

将逻辑移到私有方法中,只在吸气剂内部做“获取引用并返回”的事情,怎么样?实际上,对于一个简单的getter函数中的static和const强制转换,我会感到非常困惑,除了极少数情况下,我会认为这很丑陋!


为了避免未定义的行为,您仍然需要const_cast。看到马丁·约克(Martin York)的回答和我的评论。
凯文

1
凯文(Kevin),马丁·约克(Martin York)
彼得·尼莫

2

使用预处理器是否作弊?

struct A {

    #define GETTER_CORE_CODE       \
    /* line 1 of getter code */    \
    /* line 2 of getter code */    \
    /* .....etc............. */    \
    /* line n of getter code */       

    // ^ NOTE: line continuation char '\' on all lines but the last

   B& get() {
        GETTER_CORE_CODE
   }

   const B& get() const {
        GETTER_CORE_CODE
   }

   #undef GETTER_CORE_CODE

};

它不像模板或强制转换那么花哨,但确实使您的意图(“这两个功能要相同”)非常明确。


1
但是随后您必须注意反斜杠(多行宏通常如此),此外,在大多数(如果不是全部)编辑器中,语法高亮也会丢失。
Ruslan

2

令我惊讶的是,有这么多不同的答案,但是几乎所有答案都依赖于繁琐的模板魔术。模板功能强大,但有时宏会简洁地击败它们。通过将两者结合在一起,通常可以实现最大的多功能性。

我写了一个宏FROM_CONST_OVERLOAD(),可以将其放在非const函数中以调用const函数。

用法示例:

class MyClass
{
private:
    std::vector<std::string> data = {"str", "x"};

public:
    // Works for references
    const std::string& GetRef(std::size_t index) const
    {
        return data[index];
    }

    std::string& GetRef(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetRef(index) );
    }


    // Works for pointers
    const std::string* GetPtr(std::size_t index) const
    {
        return &data[index];
    }

    std::string* GetPtr(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetPtr(index) );
    }
};

简单且可重用的实现:

template <typename T>
T& WithoutConst(const T& ref)
{
    return const_cast<T&>(ref);
}

template <typename T>
T* WithoutConst(const T* ptr)
{
    return const_cast<T*>(ptr);
}

template <typename T>
const T* WithConst(T* ptr)
{
    return ptr;
}

#define FROM_CONST_OVERLOAD(FunctionCall) \
  WithoutConst(WithConst(this)->FunctionCall)

说明:

如许多答案中所述,在非const成员函数中避免代码重复的典型模式是:

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

使用类型推断可以避免很多这种样板。首先,const_cast可以封装在中WithoutConst(),以推断其参数类型并删除co​​nst限定符。其次,可以使用类似的方法WithConst()this指针进行const限定,从而可以调用const重载方法。

其余的是一个简单的宏,它以正确的限定条件为调用添加前缀,this->并从结果中删除const。由于宏中使用的表达式几乎总是带有1:1转发参数的简单函数调用,因此不会出现诸如多重求值之类的宏的缺点。__VA_ARGS__也可以使用省略号和,但由于逗号(如参数分隔符)出现在括号内。

这种方法有几个好处:

  • 最小自然的语法-只需将调用包装 FROM_CONST_OVERLOAD( )
  • 无需额外的成员功能
  • 与C ++ 98兼容
  • 实现简单,无需模板元编程且零依赖
  • 扩展性:其他常量关系可以添加(如const_iteratorstd::shared_ptr<const T>等)。为此,只需WithoutConst()为相应类型重载即可。

局限性:该解决方案针对非const重载与const重载完全相同的情况进行了优化,因此可以将参数1:1转发。如果您的逻辑有所不同,并且您没有通过调用const版本this->Method(args),则可以考虑其他方法。


2

对于那些像我这样的人

  • 使用c ++ 17
  • 想要添加最少的样板 /重复和
  • 不要介意使用(在等待元类时...),

这是另一种做法:

#include <utility>
#include <type_traits>

template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};

#define NON_CONST(func)                                                     \
    template <typename... T> auto func(T&&... a)                            \
        -> typename NonConst<decltype(func(std::forward<T>(a)...))>::type   \
    {                                                                       \
        return const_cast<decltype(func(std::forward<T>(a)...))>(           \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

它基本上是@ Pait,@ DavidStone和@ sh1的答案的混合体(编辑:和@cdhowie的改进)。它增加了表格的功能,使您仅需简单地为函数命名(但没有参数或返回类型重复)的一行额外代码即可:

class X
{
    const Z& get(size_t index) const { ... }
    NON_CONST(get)
};

注意:gcc无法在8.1之前的版本中进行编译,clang-5和更高版本以及MSVC-19对此感到满意(根据编译器浏览器)。


这对我来说是直截了当的。这是一个很好的答案,谢谢!

如果不是decltype()S还可以使用std::forward上的参数,以确保我们在我们的过载的情况下使用正确的返回类型get()是需要不同类型的引用的?
cdhowie

@cdhowie您能举个例子吗?
axxel

@axxel人为地狱,可是您去了。该NON_CONST宏推断返回类型错误和const_castS到错误的类型由于缺少转发的decltype(func(a...))类型。用替换它们可以decltype(func(std::forward<T>(a)...)) 解决此问题。(因为我从未定义任何声明的X::get重载,所以只有一个链接器错误。)
cdhowie,

1
感谢@cdhowie,我为您的示例提供了实际使用非const重载的方法:coliru.stacked-crooked.com/a/0cedc7f4e789479e
axxel

1

这是模板静态帮助程序函数的C ++ 17版本,带有可选的SFINAE测试。

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

完整版本:https//godbolt.org/z/mMK4r3


1

我想出了一个宏,该宏会自动生成一对const / non-const函数。

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

有关实现,请参见答案结尾。

的参数MAYBE_CONST重复。在第一个副本中,CV什么也没有替换;在第二个副本中,它被替换为const

CV宏参数中可以出现多少次没有限制。

不过有一点不便。如果CV出现在括号内,则这对括号必须以CV_IN:为前缀

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

实现方式:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

不支持的C ++ 20之前的实现CV_IN

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end

0

通常,需要const和非const版本的成员函数是getter和setter。在大多数情况下,它们是单行的,因此代码重复不是问题。


2
在大多数时候,这可能是正确的。但是也有例外。
凯文

1
无论如何,getters,const setter没什么意义;)
jwfearn

我的意思是说,非常量获取器实际上是一个setter。:)
Dima

0

我是为一个有正当理由使用const_cast... 的朋友做的……不知道我可能会做这样的事情(不是很优雅):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}

0

我建议使用私有助手静态函数模板,如下所示:

class X
{
    std::vector<Z> vecZ;

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};

-1

这篇DDJ文章展示了一种使用模板特化的方法,不需要您使用const_cast。对于这样一个简单的功能,实际上并不需要它。

boost :: any_cast(在某一时刻,不再使用)使用const版本的const_cast调用非const版本,以避免重复。但是,您不能在非const版本上强加const语义,因此您必须非常小心。

最后,只要两个代码片段彼此直接重叠,可以进行一些代码复制。


DDJ文章似乎指的是迭代器-与问题无关。常量迭代器不是常量数据,它们是指向常量数据的迭代器。
凯文

-1

要添加到jwfearn和kevin提供的解决方案中,以下是该函数返回shared_ptr时的相应解决方案:

struct C {
  shared_ptr<const char> get() const {
    return c;
  }
  shared_ptr<char> get() {
    return const_pointer_cast<char>(static_cast<const C &>(*this).get());
  }
  shared_ptr<char> c;
};

-1

没有找到我想要的东西,所以我自己滚了几个...

这个有点罗word,但具有一次处理多个相同名称(和返回类型)的重载方法的优点:

struct C {
  int x[10];

  int const* getp() const { return x; }
  int const* getp(int i) const { return &x[i]; }
  int const* getp(int* p) const { return &x[*p]; }

  int const& getr() const { return x[0]; }
  int const& getr(int i) const { return x[i]; }
  int const& getr(int* p) const { return x[*p]; }

  template<typename... Ts>
  auto* getp(Ts... args) {
    auto const* p = this;
    return const_cast<int*>(p->getp(args...));
  }

  template<typename... Ts>
  auto& getr(Ts... args) {
    auto const* p = this;
    return const_cast<int&>(p->getr(args...));
  }
};

如果const每个名称只有一个方法,但是仍然有很多方法可以重复,那么您可能更喜欢这样:

  template<typename T, typename... Ts>
  auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
    return const_cast<T*>((this->*f)(args...));
  }

  int* getp_i(int i) { return pwrap(&C::getp_i, i); }
  int* getp_p(int* p) { return pwrap(&C::getp_p, p); }

不幸的是,当您开始重载名称时,这种情况就会消失(此时函数指针参数的参数列表似乎尚未解析,因此找不到与函数参数匹配的对象)。尽管您也可以通过模板来实现:

  template<typename... Ts>
  auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }

但是,该const方法的引用自变量无法与模板的明显按值自变量相匹配,并且会中断。 不知道为什么。这就是为什么

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.