유사한 const 함수와 non-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
기본 유형의 경우 절대적으로 정확합니다! 내 첫 번째 예는별로 좋지 않았습니다. 대신 우리는 대신 클래스 클래스를 반환한다고 가정 해 봅시다. (나는 이것을 반영하기 위해 질문을 업데이트했다.)
Kevin

답변:


189

자세한 내용은 "복제 const및 비 const멤버 기능 피하기"페이지의 제목을 참조하십시오 . 23, 항목 3에서 " const가능할 때마다 사용 " , Scott Meyers가 제작 한 Effective C ++ , 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
아무도 :-) 스콧 마이어스를 다음과 같은 사항에 대해 해고되었다
스티브 Jessop

11
witkamp는 일반적으로 const_cast를 사용하는 것이 좋지 않다는 것이 맞습니다. Meyers가 설명하는 것처럼 그렇지 않은 특정 사례입니다. @ 아담 : ROM => const는 괜찮습니다. const == ROM은 누구나 불변을 const willy-nilly로 캐스팅 할 수 있기 때문에 분명히 말도 안됩니다. 그것은 무언가를 수정하지 않기로 선택하는 것과 같습니다.
Steve Jessop

44
일반적으로 static_cast 대신 const_cast를 사용하여 실수로 유형을 변경하지 못하게하기 때문에 const를 추가하는 것이 좋습니다.
Greg Rogers

6
@HelloGoodbye : 나는 마이어스는 가정 생각 소량 클래스 인터페이스의 디자이너에서 지능을. 경우 get()constCONST 객체로 정의 하였다 반환 뭔가, 다음의 const가 아닌 버전이 안 get()모두에서. 사실이 내 생각은 시간이 지남에 변경되었습니다 템플릿 솔루션은 유일한 피하기 복제하는 방법 컴파일러 확인 const를-정확성을 얻을, 그래서 개인적으로 내가 더 이상 사용하지 것이다 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());
케이시 Rodarmor

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 및 non-cost 멤버 함수에서 중복 방지"제목 아래의 효과적인 C ++ , 3d ed, 항목 3을 참조하십시오 .
jwfearn

17
솔루션이보기 흉한 것을 알고 있지만 반환 할 항목을 결정하는 코드의 길이가 50 줄이라고 상상해보십시오. 그런 다음 중복은 바람직하지 않습니다. 특히 코드를 리팩터링해야 할 때 특히 그렇습니다. 나는 내 경력 에서이 문제가 여러 번 발생했습니다.
Kevin

8
이것과 Meyers의 차이점은 Meyers에 static_cast <const X &> (* this)가 있다는 것입니다. const_cast는 const가 아닌 const를 제거하기위한 것입니다.
Steve Jessop

8
@VioletGiraffe 우리는 객체가 const가 아닌 const 객체의 비 const 멤버이기 때문에 객체가 원래 const가 아니라는 것을 알고 있습니다. 컴파일러는 이러한 추론을하지 않으며 보수적 인 규칙을 따릅니다. 이런 상황이 아니라면 왜 const_cast가 존재한다고 생각합니까?
Caleth

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&.
최대 Truxa

1
@ MaxTruxa : 그렇습니다. 이것은 좋은 것입니다. 방금 컴파일하면 매달려있는 참조가 생깁니다. f()returns 의 경우 T두 개의 과부하를 원하지 않으면 const버전만으로 충분합니다.
David Stone

매우 사실, 나는 어제 온전한 뇌 방귀에 대해 사과하며, 그 의견을 쓸 때 어떤 생각을했는지 전혀 모른다. const / mutable getter pair를 반환하는 것을보고 shared_ptr있었습니다. 그래서 실제로 필요한 것은 위와 as_mutable_ptr거의 동일하게 보이는 것입니다 . 단, 대신 as_mutablea shared_ptr를 사용하고 std::const_pointer_cast대신 사용 합니다 const_cast.
Max Truxa

1
메소드가 반환 T const*하면 바인딩에 바인딩 T const* const&&하지 않고 바인딩합니다 T const* const&(적어도 내 테스트에서는). T const*포인터를 반환하는 메소드의 인수 유형으로에 대한 과부하를 추가해야했습니다 .
monkey0506

2
@ monkey0506 : 포인터와 참조를 지원하기 위해 답변을 업데이트했습니다
David Stone

34

Scott Meyers의 솔루션은 임시 도우미 함수를 사용하여 C ++ 11에서 개선 될 수 있다고 생각합니다. 이것은 의도를 훨씬 더 분명하게 만들고 많은 다른 게터에게 재사용 할 수 있습니다.

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

첫 번째 인수는 항상 this 포인터입니다. 두 번째는 호출 할 멤버 함수에 대한 포인터입니다. 그런 다음 임의의 양의 추가 인수를 전달하여 함수로 전달할 수 있습니다. variadic 템플릿으로 인해 C ++ 11이 필요합니다.


3
우리가 std::remove_bottom_const함께 할 필요가없는 것은 부끄러운 일 입니다 std::remove_const.
TBBle 2019

이 솔루션은 여전히을 포함하기 때문에 마음에 들지 않습니다 const_cast. 당신이 만들 수있는 getElement템플릿 자체를, 그리고에 형 내부의 특성을 사용하여 mpl::conditional같은 당신이 필요로하는 유형 iterator의 또는 constiterator필요한 경우의. 실제 문제는 서명 의이 부분을 템플릿 화 할 수 없을 때 메소드의 const 버전을 생성하는 방법입니다.
v.oddou

2
@ v.oddou : std::remove_const<int const&>is int const &(최상위 const자격 제거) 이므로이 NonConst<T>답변 의 체조 . 추정 std::remove_bottom_const은 최하위 const자격을 제거하고 NonConst<T>여기서 정확히 무엇을 수행 할 수 있습니까? std::remove_bottom_const<int const&>::type=> int&.
TBBle

4
getElement과부하 된 경우이 솔루션이 제대로 작동하지 않습니다 . 그런 다음 템플릿 매개 변수를 명시 적으로 제공하지 않으면 함수 포인터를 확인할 수 없습니다. 왜?
John

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

메이어보다 조금 더 장황하지만, 나는 이것을 할 수 있습니다 :

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 인스턴스에 대해 non-const Z &를 리턴하는 바람직하지 않은 특성을 가지므로 private입니다. 전용 메소드는 외부 인터페이스의 불변을 깰 수 있습니다 (이 경우 원하는 불변은 "const 객체는 그것을 통해 얻은 객체에 대한 참조를 통해 수정할 수 없습니다").

주석은 패턴의 일부입니다-_getZ의 인터페이스는 (접근자를 제외하고는) 그것을 호출하는 것이 결코 유효하지 않다고 지정합니다. 더 작거나 빠른 코드를 생성합니다. 메소드를 호출하는 것은 const_cast를 사용하여 접근 자 중 하나를 호출하는 것과 동일하며 그렇게하지 않으려 고합니다. 오류를 명백하게하는 것에 대해 걱정이된다면 (그리고 그것은 공정한 목표입니다), _getZ 대신 const_cast_getZ를 호출하십시오.

그건 그렇고, Meyers의 솔루션에 감사드립니다. 나는 철학적으로 반대하지 않습니다. 개인적으로, 나는 약간의 제어 된 반복과 라인 노이즈처럼 보이는 방법에 대해 엄격하게 통제 된 특정 상황에서만 호출되어야하는 개인용 방법을 선호합니다. 독을 골라 내십시오.

[편집 : Kevin은 _getZ가 getZ와 같은 방식으로 const-specialized 된 추가 메소드 (예 : generateZ)를 호출 할 수 있다고 올바르게 지적했습니다. 이 경우 _getZ는 const Z &를보고 리턴하기 전에 const_cast해야합니다. 상용구 접근자가 모든 것을 감시하기 때문에 여전히 안전하지만 그것이 안전하다는 것은 눈에 띄지 않습니다. 또한 그렇게하면 나중에 const를 반환하도록 generateZ를 변경하면 항상 const를 반환하도록 getZ를 변경해야하지만 컴파일러는 그렇게 지시하지 않습니다.

컴파일러에 대한 후자의 점은 Meyers의 권장 패턴에도 해당되지만 명백하지 않은 const_cast에 대한 첫 번째 점은 그렇지 않습니다. 따라서 균형에서 _getZ가 반환 값으로 const_cast가 필요한 것으로 판명되면이 패턴은 Meyers보다 많은 가치를 잃습니다. Meyers와 비교할 때 단점이 있기 때문에 그 상황에서 그에게로 전환 할 것이라고 생각합니다. 하나에서 다른 것으로 리팩토링하는 것은 쉽다. 유효하지 않은 코드와 상용구 만 _getZ를 호출하기 때문에 클래스의 다른 유효한 코드에는 영향을 미치지 않는다.


3
이것은 여전히 ​​X의 상수 인스턴스에 대해 반환하는 것이 일정하다는 문제가 있습니다.이 경우 여전히 _getZ (...)에 const_cast가 필요합니다. 이후 개발자가 잘못 사용하더라도 여전히 UB로 이어질 수 있습니다. 반환되는 것이 '변경 가능'이면 이것이 좋은 해결책입니다.
Kevin

1
헤더 파일과 Doxygen 등에서 유효한 사용에 대한 BLOCK CAPITAL 명령을 무시하기로 선택한 경우 모든 개인 기능 (허크, 공개 기능도)은 이후 개발자가 잘못 사용할 수 있습니다. 지침을 이해하기 쉽기 때문에 내 문제로 간주하지 않습니다.
Steve Jessop

13
-1 : 많은 상황에서 작동하지 않습니다. 어떤 경우 something에서 _getZ()인스턴스 변수는 함수? 컴파일러 (또는 적어도 일부 컴파일러) _getZ()는 const이므로 const 내에서 참조되는 인스턴스 변수도 const 라고 불평합니다 . 그러면 somethingconst가되고 (유형이 const Z&) 변환 할 수 없습니다 Z&. 내 (약간 다소 제한적 인) 경험에서, 대부분의 something경우 이와 같은 경우 인스턴스 변수입니다.
중력

2
@GravityBringer : "무언가"는을 포함해야합니다 const_cast. 그것은 코드가없는 무엇을위한 장소 홀더 같이 const를 개체에서 const가 아닌 수익을 얻을 필요를위한 장소 홀더 의도 된 것이다 중복 게터에 있었다. 따라서 "무언가"는 단순한 인스턴스 변수가 아닙니다.
Steve Jessop

2
내가 참조. 그래도 기술의 유용성이 줄어 듭니다. downvote를 제거했지만 그렇게 할 수는 없습니다.
중력

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

gd1 : 정확히 내가 생각한 것. @kfsone과 정확히 내가 결론을 내린 것.
v.oddou

1
@kfsone 구문은 this키워드를 통합해야합니다 . template< typename T > auto myfunction(T this, t args) -> decltype(ident)this 키워드는 암시 적 객체 인스턴스 인수로 인식되어 컴파일러가 myfunction이 멤버 또는임을 인식하도록 제안 합니다 T. T콜 사이트에서 자동 추론됩니다. 콜 사이트는 항상 클래스의 유형이지만 무료 CV 자격이 있습니다.
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 및 non-const 버전) 또는이 기능을 친구로 만들 수 있습니다. 그러나 나는 친구를 좋아하지 않습니다.

[편집 : 테스트 중에 추가 된 불필요한 cstdio 포함 제거]


3
복잡한 구현 함수를 항상 정적 멤버로 만들어 개인 멤버에 액세스 할 수 있습니다. 함수는 클래스 헤더 파일에서만 선언하면되며 정의는 클래스 구현 파일에있을 수 있습니다. 결국 클래스 구현의 일부입니다.
CB Bailey

아 그래, 좋은 생각이야! 헤더에 서식 파일이 표시되는 것을 좋아하지 않지만 여기에서 잠재적으로 묵시를 훨씬 간단하게 만들면 가치가 있습니다.
Andy Balaam

+ 1이 솔루션은 코드를 복제하지 않으며 추한 것을 사용하지 않습니다 const_cast(실수로 사용 되지 않는 것으로 실제로 추측 할 수있는 실수로 사용될 수 있음 ).
HelloGoodbye

요즘 템플릿에 대한 추론 된 반환 형식으로 단순화 할 수 있습니다 (특히 멤버 사례에서 클래스에서 복제해야하는 항목이 줄어들 기 때문에 특히 유용합니다).
Davis Herring

3

논리를 개인 메소드로 옮기고 게터 내에서 "참조 및 리턴 얻기"만 수행하는 것은 어떻습니까? 실제로 간단한 getter 함수 내부의 정적 및 const 캐스트에 대해 상당히 혼란 스러울 것입니다. 매우 드문 상황을 제외하고는 추악한 것으로 간주합니다!


정의되지 않은 동작을 피하려면 const_cast가 필요합니다. Martin York의 답변과 거기에 대한 내 의견을 참조하십시오.
Kevin

1
Kevin, Martin York의 답변
Peter Nimmo

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()인수의 유형을 유추하고 const 한정자를 제거합니다. 둘째, 포인터 WithConst()를 const 한정하기 위해 비슷한 접근법을 사용할 수 있으며 thisconst 오버로드 된 메소드를 호출 할 수 있습니다.

나머지는 올바른 접두사로 호출 앞에 접두사를 붙이고 this->결과에서 const를 제거 하는 간단한 매크로입니다 . 매크로에 사용 된 표현식은 거의 항상 1 : 1 전달 인수를 사용하는 간단한 함수 호출이므로 다중 평가와 같은 매크로의 단점은 발생하지 않습니다.__VA_ARGS__ 사용할 수도 있지만 쉼표로 인해 필요하지 않아야합니다 ( 인수 구분 기호)는 괄호 안에 표시됩니다.

이 방법에는 몇 가지 이점이 있습니다.

  • 최소한의 자연스러운 구문 FROM_CONST_OVERLOAD( )
  • 추가 멤버 기능이 필요하지 않습니다
  • C ++ 98과 호환
  • 간단한 구현, 템플릿 메타 프로그래밍 및 종속성 없음
  • 확장 가능 : 다른 const 관계를 추가 할 수 있습니다 (예 const_iterator: std::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의 답변이 혼합되어 있습니다 ( EDIT : @cdhowie의 개선 사항). 테이블에 추가하는 것은 단순히 함수의 이름을 지정하는 추가 코드 줄 하나만으로 얻을 수 있다는 것입니다 (그러나 인수 또는 반환 유형 복제는 없음).

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

참고 : gcc는 8.1 이전의 컴파일을 실패합니다 .clang-5 이상은 MSVC-19에 만족합니다 ( 컴파일러 탐색기 에 따라 ).


이것은 나를 위해 곧장 일했습니다. 이것은 훌륭한 답변입니다, 감사합니다!
Short

decltype()s 가 다른 유형의 참조를 취하는 std::forward과부하가있는 경우 올바른 반환 유형을 사용하도록 인수에 사용 해서는 안 get()됩니까?
cdhowie

@cdhowie 예제를 제공해 줄 수 있습니까?
axxel

@axxel 그것은 지옥으로 고안되었지만 여기에 있습니다 . NON_CONST반환 형식이 잘못 매크로 추론 const_cast인한에서 전달의 부족으로 잘못된 유형이야 decltype(func(a...))유형. 그것들을 교체하면 decltype(func(std::forward<T>(a)...)) 해결 됩니다. (선언 된 X::get오버로드를 정의하지 않았기 때문에 링커 오류가 있습니다.)
cdhowie

1
@cdhowie에게 감사드립니다. 실제로는 비 const 과부하를 사용하도록 예제를 단순화했습니다
axxel

1

다음은 선택적 SFINAE 테스트가 포함 된 템플릿 정적 도우미 함수의 C ++ 17 버전입니다.

#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 및 non-const 버전이 필요한 멤버 함수는 getter 및 setter입니다. 대부분의 경우 그들은 하나의 라이너이므로 코드 복제는 문제가되지 않습니다.


2
대부분의 경우에 해당 될 수 있습니다. 그러나 예외가 있습니다.
Kevin

1
어쨌든 getters, const setter는별로 의미가 없습니다.)
jwfearn

비 const 게터는 실제로 세터라는 것을 의미했습니다. :)
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 버전의 const_cast를 사용하여 중복을 피합니다. 비 const 버전에서 const 의미를 부과 할 수는 없으므로 매우 해야합니다. 주의해야합니다.

결국 두 개의 스 니펫이 서로 바로 위에있는 한 일부 코드 복제 괜찮습니다.


DDJ 기사는 반복자와 관련이있는 것으로 보입니다.이 질문과 관련이 없습니다. 상수 반복자는 상수 데이터가 아니며 상수 데이터를 가리키는 반복자입니다.
Kevin

-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

내가 찾고있는 것을 찾지 못해서 내 자신의 부부를 굴 렸습니다 ...

이것은 약간 장황하지만 같은 이름 (및 반환 유형)의 많은 오버로드 된 메소드를 한 번에 처리 할 수 ​​있다는 이점이 있습니다.

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메소드에 대한 참조 인수 는 템플리트에 대한 명백한 값별 인수와 일치하지 않으며 중단됩니다. 이유가 확실하지 않습니다. 이유는 다음과 같습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.