문제

다음과 같이 가정 해 봅시다 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 버팀대 안에 동일한 코드가 있습니다. 이것은 중복 코드입니다 복잡한 논리로 긴 기능에 대한 유지 보수 문제를 일으킬 수 있습니다..

이 코드 복제를 피할 수있는 방법이 있습니까?

도움이 되었습니까?

해결책 2

예, 코드 복제를 피할 수 있습니다. 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- 기능이 정의되지 않은 함수를 호출하도록하십시오. 정의되지 않은 동작이 발생할 수 있습니다. 그 이유는 일정한 클래스 인스턴스가 비정상적인 인스턴스로 캐스트되기 때문입니다. 비 정통 멤버 함수는 실수로 클래스를 수정할 수 있으며, C ++ 표준 상태는 정의되지 않은 동작을 초래합니다.

다른 팁

자세한 설명은 제목 "복제 방지"를 참조하십시오. const 그리고 비const 회원 함수, "23 페이지, 항목 3"사용 const 가능할 때마다 "in 효과적인 C ++, Scott Meyers의 3d Ed, ISBN-13 : 9780321334879.

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

두 개의 캐스트와 기능 호출은 못 생겼지 만 맞습니다. Meyers는 이유를 철저히 설명합니다.

Scott Meyers의 솔루션은 Tempate 도우미 기능을 사용하여 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);
   }
};

첫 번째 인수는 항상이 포인터입니다. 두 번째는 호출 할 멤버 함수에 대한 포인터입니다. 그 후에는 임의의 양의 추가 인수가 전달되어 함수로 전달 될 수 있습니다. Variadic 템플릿으로 인해 C ++ 11이 필요합니다.

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

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 &를 반환하는 바람직하지 않은 속성이 있으므로 개인입니다. 개인 메소드는 외부 인터페이스의 불변량을 깨뜨릴 수 있습니다 (이 경우 원하는 불변량은 "원하는 객체는 객체를 통해 얻은 참조를 통해 수정할 수 없습니다.").

주석은 패턴의 일부입니다. _Getz의 인터페이스는 (액세서리를 제외하고는 분명히) 호출하는 것이 유효하지 않음을 지정합니다. 어쨌든 그렇게 할 수있는 이점은 없습니다. 더 작거나 빠른 코드가 발생합니다. 이 방법을 호출하는 것은 Const_cast로 액세서 중 하나를 호출하는 것과 같습니다. 오류를 명백히 만드는 것이 걱정된다면 (그리고 이것이 공정한 목표입니다), _getz 대신 const_cast_getz라고 부릅니다.

그건 그렇고, 나는 Meyers의 솔루션에 감사드립니다. 나는 그것에 대해 철학적 반대 의견이 없습니다. 그러나 개인적으로, 나는 약간의 통제 된 반복과 라인 노이즈처럼 보이는 방법보다 단단히 통제 된 상황에서만 호출 해야하는 개인 방법을 선호합니다. 독을 고르고 고집하십시오.

편집 : Kevin은 _getz가 Getz와 같은 방식으로 구성된 추가 방법 (Generatez)을 호출하고 싶을 수도 있다고 지적했습니다. 이 경우 _getz는 const Z &를보고 반환 전에 const_cast를해야합니다. 보일러 플레이트 액세서가 모든 것을 정책하기 때문에 여전히 안전하지만 안전하다는 것은 놀라운 일이 아닙니다. 또한, 당신이 그렇게하고 나중에 generatez를 변경하여 항상 const를 반환하기 위해 Generatez를 변경하면 항상 const를 반환하기 위해 Getz를 변경해야하지만 컴파일러는 당신이 그렇게한다고 말하지 않습니다.

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

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>
void as_mutable(T const &&) = delete;

이제 당신은 엉망이 될 수 없습니다 volatile, 그리고 사용은 모양입니다

T & f() {
    return as_mutable(std::as_const(*this).f());
}

좋은 질문과 좋은 대답. 캐스트가없는 다른 솔루션이 있습니다.

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 내부에 변수.

나는이 솔루션의 가능한 모든 (부정적인) 의미를 고려하지 않았다. 무엇이든 알려주세요.

템플릿으로 이것을 해결할 수도 있습니다. 이 솔루션은 약간 추악하지만 (추악함은 .CPP 파일에 숨겨져 있음) Constness의 컴파일러 점검을 제공하며 코드 복제는 없습니다.

.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 및 nonst 버전) 또는이 기능을 친구로 만들 수 있습니다. 하지만 나는 친구를 좋아하지 않는다.

편집 : 테스트 중에 추가 된 CSTDIO에 대한 불필요한 포함 포함 제거.

논리를 비공개 방법으로 옮기고 "참조를 얻고 반환"을 게터 내부에서만 수행하는 것은 어떻습니까? 실제로, 나는 간단한 getter 기능 안에서 정적과 const 캐스트에 대해 상당히 혼란 스러울 것이며, 극도로 드문 상황을 제외하고는 추악한 것을 고려할 것입니다!

사전 처리기를 사용하는 것이 속임수입니까?

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

};

템플릿이나 캐스트만큼 화려하지는 않지만 의도 ( "이 두 기능은 동일해야한다")는 꽤 명백하게 만듭니다.

일반적으로 Const 및 비 초보 버전이 필요한 멤버 기능은 Getters 및 Setter입니다. 대부분의 경우 그들은 하나의 라이너이므로 코드 복제는 문제가되지 않습니다.

나는 사용을 정당하게 정당화 한 친구를 위해 이것을했다. 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;
}

다음과 같은 개인 도우미 정적 기능 템플릿을 제안합니다.

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

나처럼 누구를 위해

  • 사용 C ++ 17
  • 추가하고 싶습니다 최소량의 보일러 플레이트/반복 및
  • 사용하지 마십시오 Makros (메타 클래스를 기다리는 동안 ...),

다음은 또 다른 테이크입니다.

#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(a...))>::type {  \
        return const_cast<decltype(func(a...))>(                            \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

기본적으로 @pait, @davidstone 및 @sh1의 답변이 혼합되어 있습니다. 테이블에 추가되는 것은 단순히 함수의 이름을 지정하는 한 줄의 추가 줄만으로 도망 치는 것입니다 (그러나 인수 또는 반환 유형 복제는 없음).

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

참고 : GCC는 8.1, Clang-5 이상 이전에 이것을 컴파일하지 못하고 MSVC-19가 행복합니다 (에 따라 컴파일러 탐색기).

많은 다른 답변이 있다는 것은 놀랍지 만 거의 모두 무거운 템플릿 마법에 의존합니다. 템플릿은 강력하지만 때로는 매크로가 간결하게 이길 수 있습니다. 최대 다양성은 종종 두 가지를 결합하여 달성됩니다.

나는 매크로를 썼다 FROM_CONST_OVERLOAD() 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)

설명:

많은 답변에 게시 된 바와 같이, 멤버가 아닌 멤버 함수에서 코드 복제를 피하기위한 일반적인 패턴은 다음과 같습니다.

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

이 보일러 플레이트의 많은 부분은 유형 추론을 사용하여 피할 수 있습니다. 첫 번째, const_cast 캡슐화 될 수 있습니다 WithoutConst(), 인수의 유형을 유추하고 Const-qualifier를 제거합니다. 둘째, 비슷한 접근법을 사용할 수 있습니다 WithConst() const-qualify this Const-Overloaded 메소드를 호출 할 수있는 포인터.

나머지는 올바르게 자격을 갖춘 호출을 접두사하는 간단한 매크로입니다. this-> 결과에서 const를 제거합니다. 매크로에 사용 된 표현은 거의 항상 1 : 1 전달 된 인수와 함께 간단한 기능 호출이기 때문에 다중 평가와 같은 매크로의 단점은 시작되지 않습니다. __VA_ARGS__ 또한 사용될 수 있지만 쉼표 (인수 분리기)가 괄호 안에 발생하기 때문에 필요하지 않아야합니다.

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

  • 최소 및 자연 구문 - 전화를 마무리하십시오. FROM_CONST_OVERLOAD( )
  • 추가 회원 기능이 필요하지 않습니다
  • C ++ 98과 호환됩니다
  • 간단한 구현, 템플릿 메타 프로 그램 및 제로 의존성
  • 확장 가능 : 다른 const 관계를 추가 할 수 있습니다 ( const_iterator, std::shared_ptr<const T>, 등.). 이를 위해서는 단순히 과부하입니다 WithoutConst() 해당 유형의 경우.

제한 사항 :이 솔루션은 비 초과 과부하가 Const 과부하와 정확히 동일하게 수행되는 시나리오에 최적화되어 1 : 1을 전달할 수 있습니다. 논리가 다르고 귀하가 Const 버전을 통해 호출하지 않는 경우 this->Method(args), 당신은 다른 접근법을 고려할 수 있습니다.

이 DDJ 기사 const_cast를 사용할 필요가없는 템플릿 전문화를 사용하는 방법을 보여줍니다. 이러한 간단한 기능의 경우 실제로는 필요하지 않습니다.

Boost :: Any_cast (한 시점에서 더 이상 없음)는 중복을 피하기 위해 Const 버전에서 Const_cast를 사용합니다. 당신은 당신이 아닌 버전에 Const Semantics를 부과 할 수는 없으므로 당신은 매우 조심하십시오.

결국 일부 코드 복제 ~이다 두 스 니펫이 서로 직접 위에있는 한 좋아.

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

내가 찾고 있던 것을 찾지 못했기 때문에 나는 내 자신의 몇 가지를 굴 렸습니다 ...

이것은 약간 말이 많지만 동일한 이름 (및 반환 유형)의 많은 과부하 메소드를 한 번에 처리하는 이점이 있습니다.

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 메소드는 템플릿에 대한 명백한 가치 인수와 일치하지 않으면 파손됩니다. 이유가 확실하지 않습니다.이유는 다음과 같습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top