如何在类似的const和非CONST成员功能之间删除代码重复?

发布于 2025-02-10 19:11:53 字数 933 浏览 3 评论 0 原文

假设我有以下类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() and x ::: Z()const 在括号内具有相同的代码。这是重复的代码,可能会导致具有复杂逻辑的长函数的维护问题。

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

Let's say I have the following class X where I want to return access to an internal member:

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

The two member functions X::Z() and X::Z() const have identical code inside the braces. This is duplicate code and can cause maintenance problems for long functions with complex logic.

Is there a way to avoid this code duplication?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(22

烟柳画桥 2025-02-17 19:11:54

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

Typically, the member functions for which you need const and non-const versions are getters and setters. Most of the time they are one-liners so code duplication is not an issue.

说不完的你爱 2025-02-17 19:11:54

为了添加到提供的解决方案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;
};

To add to the solution jwfearn and kevin provided, here's the corresponding solution when the function returns 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;
};
神爱温柔 2025-02-17 19:11:54

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

这是有点言行,但是有一个优势,可以同时处理许多同名的超载方法(和返回类型):

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 方法无法与显然的串联参数匹配到模板,并且会破裂。 不确定为什么。 这就是为什么

Didn't find what I was looking for, so I rolled a couple of my own...

This one is a little wordy, but has the advantage of handling many overloaded methods of the same name (and return type) all at once:

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

If you have only one const method per name, but still plenty of methods to duplicate, then you might prefer this:

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

Unfortunately this breaks down as soon as you start overloading the name (the function pointer argument's argument list seems to be unresolved at that point, so it can't find a match for the function argument). Although you can template your way out of that, too:

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

But reference arguments to the const method fail to match against the apparently by-value arguments to the template and it breaks. Not sure why.Here's why.

何以畏孤独 2025-02-17 19:11:54

我做了一个宏

#include <utility>

// Based on https://en.cppreference.com/mwiki/index.php?title=cpp/utility/as_const&oldid=151976#Possible_implementation
template <typename T> constexpr T &as_non_const(const T &t) noexcept { return const_cast<T &>(t); }

#define NON_CONST_GETTER(const_getter)                                                                                 \
  auto &const_getter() { return as_non_const(std::as_const(*this).const_getter()); }

// Example
class C {
private:
  int secret = 42;
public:
  const int &get() const { return secret; }
  NON_CONST_GETTER(get);
};

I made a macro
https://gist.github.com/esnosy/947565a4e651991dc6a25f3e9f467285

#include <utility>

// Based on https://en.cppreference.com/mwiki/index.php?title=cpp/utility/as_const&oldid=151976#Possible_implementation
template <typename T> constexpr T &as_non_const(const T &t) noexcept { return const_cast<T &>(t); }

#define NON_CONST_GETTER(const_getter)                                                                                 \
  auto &const_getter() { return as_non_const(std::as_const(*this).const_getter()); }

// Example
class C {
private:
  int secret = 42;
public:
  const int &get() const { return secret; }
  NON_CONST_GETTER(get);
};
浪菊怪哟 2025-02-17 19:11:54

此DDJ文章显示了一种使用模板专业化的方式,该模板专业不需要您使用const_cast。对于如此简单的功能,确实不需要。

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

最后,只要两个摘要直接彼此顶部,那么某些代码重复可以的。

This DDJ article shows a way using template specialization that doesn't require you to use const_cast. For such a simple function it really isn't needed though.

boost::any_cast (at one point, it doesn't any more) uses a const_cast from the const version calling the non-const version to avoid duplication. You can't impose const semantics on the non-const version though so you have to be very careful with that.

In the end some code duplication is okay as long as the two snippets are directly on top of each other.

诺曦 2025-02-17 19:11:53

有关详细说明,请参见p的标题“避免在 const 和non- const 成员函数中重复”。 23,在第3项“使用 const ”中,在,3d Ed Scott Meyers,ISBN-13:9780321334879

。 jpg“ alt =“ alt text”>

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

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

两个铸件和函数调用可能很丑 const 开始。 (迈耶斯对此有详尽的讨论。)

For a detailed explanation, please see the heading "Avoid Duplication in const and Non-const Member Function," on p. 23, in Item 3 "Use const whenever possible," in Effective C++, 3d ed by Scott Meyers, ISBN-13: 9780321334879.

alt text

Here's Meyers' solution (simplified):

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

The two casts and function call may be ugly, but it's correct in a non-const method as that implies the object was not const to begin with. (Meyers has a thorough discussion of this.)

黒涩兲箜 2025-02-17 19:11:53

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

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

这具有以下优点:

  • 显而易见的是,
  • 在开销上有最小的代码 - 它适合一行
  • 很难弄错(只能抛弃波动性是偶然的,但是 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;

,现在您甚至都不会搞砸挥发性,用法看起来像

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

C++17 has updated the best answer for this question:

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

This has the advantages that it:

  • Is obvious what is going on
  • Has minimal code overhead -- it fits in a single line
  • Is hard to get wrong (can only cast away volatile by accident, but volatile is a rare qualifier)

If you want to go the full deduction route then that can be accomplished by having a helper function

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;

Now you can't even mess up volatile, and the usage looks like

decltype(auto) f() const {
    return something_complicated();
}
decltype(auto) f() {
    return as_mutable(std::as_const(*this).f());
}
他夏了夏天 2025-02-17 19:11:53

是的,可以避免代码重复。您需要使用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
};

< strong>注意:重要的是,您要做 将逻辑放在非const函数中并具有const函数呼叫非const函数 - 可能会导致未定义行为。原因是恒定类实例被施放为非恒定实例。非CONST成员函数可能会意外修改类别,C ++标准状态将导致不确定的行为。

Yes, it is possible to avoid the code duplication. You need to use the const member function to have the logic and have the non-const member function call the const member function and re-cast the return value to a non-const reference (or pointer if the functions returns a pointer):

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

NOTE: It is important that you do NOT put the logic in the non-const function and have the const-function call the non-const function -- it may result in undefined behavior. The reason is that a constant class instance gets cast as a non-constant instance. The non-const member function may accidentally modify the class, which the C++ standard states will result in undefined behavior.

与他有关 2025-02-17 19:11:53

C ++ 23已更新了此问题的最佳答案,这要归功于

struct s {
    auto && f(this auto && self) {
        // all the common code goes here
    }
};

单个功能模板可作为普通成员函数可调用,并为您推论正确的参考类型。没有铸造犯错的东西,也没有为概念上一件事的事情编写多个功能。


注意:此功能由 p0847:deeduc this

C++23 has updated the best answer for this question thanks to explicit object parameters.

struct s {
    auto && f(this auto && self) {
        // all the common code goes here
    }
};

A single function template is callable as a normal member function and deduces the correct reference type for you. No casting to get wrong, no writing multiple functions for something that is conceptually one thing.


Note: this feature was added by P0847: Deducing this.

じее 2025-02-17 19:11:53

我认为可以使用Tempate Helper功能在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。

I think Scott Meyers' solution can be improved in C++11 by using a tempate helper function. This makes the intent much more obvious and can be reused for many other getters.

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

This helper function can be used the following way.

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

The first argument is always the this-pointer. The second is the pointer to the member function to call. After that an arbitrary amount of additional arguments can be passed so that they can be forwarded to the function.
This needs C++11 because of the variadic templates.

叹沉浮 2025-02-17 19:11:53

好的问题和不错的答案。我还有另一种使用不使用铸件的解决方案:

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

};

但是,它具有需要静态成员的丑闻,并且需要在其中使用实例变量。

我没有考虑该解决方案的所有可能(负)含义。请让我知道是否有。

Nice question and nice answers. I have another solution, that uses no casts:

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

};

However, it has the ugliness of requiring a static member and the need of using the instance variable inside it.

I did not consider all the possible (negative) implications of this solution. Please let me know if any.

最冷一天 2025-02-17 19:11:53

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

私人方法具有不良属性,它返回了非const Z&amp;对于const实例,这就是为什么它是私人的。私有方法可能会破坏外部接口的不变性(在这种情况下,所需的不变是“无法通过通过其通过其引用到其has-a的对象获得的引用来修改const对象”)。

请注意,这些评论是模式的一部分-_getz的界面指定它永远不会有效(显然,除了访问者之外):无论如何,都没有可以想象的好处导致较小或更快的代码。调用该方法等同于用const_cast调用其中一个访问者,您也不想这样做。如果您担心错误的错误(这是一个公平的目标),请称其为const_cast_getz,而不是_getz。

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

[编辑:凯文(Kevin)正确地指出,_getz可能想调用进一步的方法(例如,generatez),该方法以相同的方式被const专业化。在这种情况下,_getz会看到const z&amp;并且必须在返回之前将其cast。这仍然是安全的,因为样板登录警策的所有内容都不是很明显的。此外,如果您这样做,然后以后将Generatez始终返回const,那么您还需要将Getz更改为始终返回const,但是编译器不会告诉您您这样做。

关于编译器的后一个观点也是Meyers推荐的模式的正确性,但是关于非显而易见的const_cast的第一点不是。因此,我认为,如果_getz遇到const_cast的返回值,那么此模式就会在Meyers上失去很多价值。由于与迈耶斯(Meyers)相比,这也遭受了缺点,我想我会在这种情况下转向他。从一个到另一个的重构很容易 - 它不会影响类中的任何其他有效代码,因为只有无效的代码和样板调用_getz。]

A bit more verbose than Meyers, but I might do this:

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

The private method has the undesirable property that it returns a non-const Z& for a const instance, which is why it's private. Private methods may break invariants of the external interface (in this case the desired invariant is "a const object cannot be modified via references obtained through it to objects it has-a").

Note that the comments are part of the pattern - _getZ's interface specifies that it is never valid to call it (aside from the accessors, obviously): there's no conceivable benefit to doing so anyway, because it's 1 more character to type and won't result in smaller or faster code. Calling the method is equivalent to calling one of the accessors with a const_cast, and you wouldn't want to do that either. If you're worried about making errors obvious (and that's a fair goal), then call it const_cast_getZ instead of _getZ.

By the way, I appreciate Meyers's solution. I have no philosophical objection to it. Personally, though, I prefer a tiny bit of controlled repetition, and a private method that must only be called in certain tightly-controlled circumstances, over a method that looks like line noise. Pick your poison and stick with it.

[Edit: Kevin has rightly pointed out that _getZ might want to call a further method (say generateZ) which is const-specialised in the same way getZ is. In this case, _getZ would see a const Z& and have to const_cast it before return. That's still safe, since the boilerplate accessor polices everything, but it's not outstandingly obvious that it's safe. Furthermore, if you do that and then later change generateZ to always return const, then you also need to change getZ to always return const, but the compiler won't tell you that you do.

That latter point about the compiler is also true of Meyers's recommended pattern, but the first point about a non-obvious const_cast isn't. So on balance I think that if _getZ turns out to need a const_cast for its return value, then this pattern loses a lot of its value over Meyers's. Since it also suffers disadvantages compared to Meyers's, I think I would switch to his in that situation. Refactoring from one to the other is easy -- it doesn't affect any other valid code in the class, since only invalid code and the boilerplate calls _getZ.]

病毒体 2025-02-17 19:11:53

对于那些

  • 使用 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的答案的混合在一起( eding> edit :以及@cdhowie的改进)。它添加到表中的是,您只使用仅命名函数的一条额外的代码(但没有参数或返回类型重复):

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

注意:GCC在8.1之前未能编译此函数,clang-5及以上为以及MSVC-19很高兴(根据编译器资源管理器)。

For those (like me) who

  • use c++17
  • want to add the least amount of boilerplate/repetition and
  • don't mind using macros (while waiting for meta-classes...),

here is another take:

#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)...));              \
    }

It is basically a mix of the answers from @Pait, @DavidStone and @sh1 (EDIT: and an improvement from @cdhowie). What it adds to the table is that you get away with only one extra line of code which simply names the function (but no argument or return type duplication):

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

Note: gcc fails to compile this prior to 8.1, clang-5 and upwards as well as MSVC-19 are happy (according to the compiler explorer).

夕色琉璃 2025-02-17 19:11:53

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

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

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

​其中始终需要有const和非const版本),或者您可以使此功能成为朋友。但是我不喜欢朋友。

[编辑:删除了不需要的包括在测试过程中添加的CSTDIO。]

You could also solve this with templates. This solution is slightly ugly (but the ugliness is hidden in the .cpp file) but it does provide compiler checking of constness, and no code duplication.

.h file:

#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 file:

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

The main disadvantage I can see is that because all the complex implementation of the method is in a global function, you either need to get hold of the members of X using public methods like GetVector() above (of which there always need to be a const and non-const version) or you could make this function a friend. But I don't like friends.

[Edit: removed unneeded include of cstdio added during testing.]

过度放纵 2025-02-17 19:11:53

如果您不喜欢 const 铸造,我使用此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

If you don't like const casting, I use this C++17 version of the template static helper function suggested by another answer, with and optional SFINAE test.

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

Full version: https://godbolt.org/z/mMK4r3

梦过后 2025-02-17 19:11:53

虽然这里的大多数答案建议使用 const_cast ,而cppcoreguidelines则具有

而是更喜欢共享实现。通常,您只需将非const函数调用const函数即可。但是,当有复杂的逻辑时,这可能会导致以下模式,该模式仍然求助于const_cast:

class Foo {
public:
    // not great, non-const calls const version but resorts to const_cast
    Bar& get_bar()
    {
        return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
    }
    const Bar& get_bar() const
    {
        /* the complex logic around getting a const reference to my_bar */
    }
private:
    Bar my_bar;
};

虽然正确应用时这种模式是安全的,因为
呼叫者一定有一个非符合对象,这不是理想的
因为安全很难自动作为检查器规则。

相反,更喜欢将公共代码放在公共辅助功能中 -
并使其成为模板,以便推论const。这不使用任何
const_cast完全:

class Foo {
public:                         // good
          Bar& get_bar()       { return get_bar_impl(*this); }
    const Bar& get_bar() const { return get_bar_impl(*this); }
private:
    Bar my_bar;

    template<class T>           // good, deduces whether T is const or non-const
    static auto& get_bar_impl(T& t)
        { /* the complex logic around getting a possibly-const reference to my_bar */ }
};

注意:不要在模板中进行大型非依赖性工作,从而导致代码膨胀。例如,进一步的改进将是,如果get_bar_impl的全部或部分可能是非依赖性的,并将其纳入一个常见的非模板函数,以实现代码大小的潜在大量减少。

While most of answers here suggest to use a const_cast, CppCoreGuidelines have a section about that:

Instead, prefer to share implementations. Normally, you can just have the non-const function call the const function. However, when there is complex logic this can lead to the following pattern that still resorts to a const_cast:

class Foo {
public:
    // not great, non-const calls const version but resorts to const_cast
    Bar& get_bar()
    {
        return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
    }
    const Bar& get_bar() const
    {
        /* the complex logic around getting a const reference to my_bar */
    }
private:
    Bar my_bar;
};

Although this pattern is safe when applied correctly, because the
caller must have had a non-const object to begin with, it's not ideal
because the safety is hard to enforce automatically as a checker rule.

Instead, prefer to put the common code in a common helper function --
and make it a template so that it deduces const. This doesn't use any
const_cast at all:

class Foo {
public:                         // good
          Bar& get_bar()       { return get_bar_impl(*this); }
    const Bar& get_bar() const { return get_bar_impl(*this); }
private:
    Bar my_bar;

    template<class T>           // good, deduces whether T is const or non-const
    static auto& get_bar_impl(T& t)
        { /* the complex logic around getting a possibly-const reference to my_bar */ }
};

Note: Don't do large non-dependent work inside a template, which leads to code bloat. For example, a further improvement would be if all or part of get_bar_impl can be non-dependent and factored out into a common non-template function, for a potentially big reduction in code size.

海的爱人是光 2025-02-17 19:11:53

如何将逻辑转移到私人方法中,而只能在Getters内​​部执行“获取参考并返回”的内容?实际上,我会对简单的getter功能内的静态和const铸造感到困惑,而且除了极少数情况下,我会认为这很丑陋!

How about moving the logic into a private method, and only doing the "get the reference and return" stuff inside the getters? Actually, I would be fairly confused about the static and const casts inside a simple getter function, and I'd consider that ugly except for extremely rare circumstances!

记忆里有你的影子 2025-02-17 19:11:53

我建议使用私人辅助静态功能模板,这样:

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

I'd suggest a private helper static function template, like this:

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);
    }
};
萌︼了一个春 2025-02-17 19:11:53

使用预处理器是作弊吗?

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

};

它不像模板或铸件那样花哨,但它确实使您的意图(“这两个功能是相同的”)非常明确。

Is it cheating to use the preprocessor?

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

};

It's not as fancy as templates or casts, but it does make your intent ("these two functions are to be identical") pretty explicit.

甩你一脸翔 2025-02-17 19:11:53

令人惊讶的是,有很多不同的答案,但几乎所有的答案都依赖于重型模板魔术。模板功能强大,但有时宏以简洁的态度击败了它们。通常通过将两者结合来实现最大多功能性。

我写了一个宏 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) );

可以使用类型推荐来避免使用许多样式板。 First, const_cast can be encapsulated in WithoutConst(), which infers the type of its argument and removes the const-qualifier.其次,可以在 with const()中使用类似的方法来const-qualify this 指针,该指针启用了调用const-overpradaded方法。

其余的是一个简单的宏,它以正确的 this-&gt; 和从结果中删除const的呼叫前缀。由于宏中使用的表达式几乎总是一个简单的函数调用,其中有1:1转发参数,因此也可以使用省略的缺点,例如多次评估。不需要,因为逗号(作为参数分离器)发生在括号内。

这种方法有几个好处:

  • 最小和自然语法 - 只需将调用包装在 from_const_overload()
  • 额外成员函数
  • 无需与C ++ 98兼容的
  • ,无需兼容,没有模板元编程和零依赖性
  • :其他。可以添加const关系(例如 const_iterator std :: shared_ptr&const t&gt; 等)。为此,只需为相应类型的conconst() Overload

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

It's surprising to me that there are so many different answers, yet almost all rely on heavy template magic. Templates are powerful, but sometimes macros beat them in conciseness. Maximum versatility is often achieved by combining both.

I wrote a macro FROM_CONST_OVERLOAD() which can be placed in the non-const function to invoke the const function.

Example usage:

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

Simple and reusable implementation:

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)

Explanation:

As posted in many answers, the typical pattern to avoid code duplication in a non-const member function is this:

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

A lot of this boilerplate can be avoided using type inference. First, const_cast can be encapsulated in WithoutConst(), which infers the type of its argument and removes the const-qualifier. Second, a similar approach can be used in WithConst() to const-qualify the this pointer, which enables calling the const-overloaded method.

The rest is a simple macro that prefixes the call with the correctly qualified this-> and removes const from the result. Since the expression used in the macro is almost always a simple function call with 1:1 forwarded arguments, drawbacks of macros such as multiple evaluation do not kick in. The ellipsis and __VA_ARGS__ could also be used, but should not be needed because commas (as argument separators) occur within parentheses.

This approach has several benefits:

  • Minimal and natural syntax -- just wrap the call in FROM_CONST_OVERLOAD( )
  • No extra member function required
  • Compatible with C++98
  • Simple implementation, no template metaprogramming and zero dependencies
  • Extensible: other const relations can be added (like const_iterator, std::shared_ptr<const T>, etc.). For this, simply overload WithoutConst() for the corresponding types.

Limitations: this solution is optimized for scenarios where the non-const overload is doing exactly the same as the const overload, so that arguments can be forwarded 1:1. If your logic differs and you are not calling the const version via this->Method(args), you may consider other approaches.

半葬歌 2025-02-17 19:11:53

我想出了一个宏,该宏会自动生成一对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;}
};

请参阅“实施”答案的结尾。

and_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)

pre-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

I came up with a macro that generates pairs of const/non-const functions automatically.

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

See the end of the answer for the implementation.

The argument of MAYBE_CONST is duplicated. In the first copy, CV is replaced with nothing; and in the second copy it's replaced with const.

There's no limit on how many times CV can appear in the macro argument.

There's a slight inconvenience though. If CV appears inside of parentheses, this pair of parentheses must be prefixed with 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 &); )

Implementation:

#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)

Pre-C++20 implementation that doesn't support 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
牛↙奶布丁 2025-02-17 19:11:53

我为一个朋友做到了这一点,他正确地证明了使用 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;
}

I did this for a friend who rightfully justified the use of const_cast... not knowing about it I probably would have done something like this (not really elegant) :

#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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文